From 51a5140091e93be5a8c58a54e530a49f70cc881c Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 16:48:30 +0800 Subject: [PATCH 1/4] refactor(adapter): register exactly one IoBinding per biz A biz now identifies one external contract. Registering a second binding for the same biz_name is a registry conflict, so SDK init fails closed (-6). This replaces the cross-binding contract comparison (CheckBizContract, ValidateBizContract and the BIZ_IO_CONTRACT_MISMATCH preflight diagnostic), which only existed to let several bindings share one biz. The documented release policy contradicted that check: it asked for a new versioned binding under the same biz on incompatible changes, which the check rejected. CONTRIBUTING now says incompatible changes after release are made in place and shipped in a new SDK release, and a new biz is added only when both contracts must coexist. Tests that attached extra bindings to production bizs now use dedicated test bizs or replace the binding. The duplicate unselected-binding audit test is removed; the equivalent-carrier test goes with the feature. Co-Authored-By: Claude Opus 5.5 --- .../edgeflow-adapter-developer/SKILL.md | 4 +- .../references/integration.md | 4 +- CONTRIBUTING.md | 7 +- configs/README.md | 2 +- doc/CHANGELOG.md | 7 +- doc/dev_guide/business_onboarding.md | 4 +- doc/dev_guide/source_layout.md | 2 +- include/adapter/io_binding_registry.h | 4 - include/adapter/io_converter.h | 2 +- include/edgeflow/operator/interface.h | 2 +- src/adapter/deployment_preparation.cpp | 11 - src/adapter/io_binding_registry.cpp | 92 +------ tests/contract/abi/test_operator_safety.cpp | 7 + .../operator/test_operator_api.cpp | 7 + tests/unit/adapter/test_adapter_purity.cpp | 13 +- .../unit/adapter/test_io_binding_registry.cpp | 242 ++++-------------- tests/unit/adapter/test_text_converters.cpp | 32 ++- 17 files changed, 120 insertions(+), 322 deletions(-) diff --git a/.agents/skills/edgeflow-adapter-developer/SKILL.md b/.agents/skills/edgeflow-adapter-developer/SKILL.md index 102fb304..689ab7f7 100644 --- a/.agents/skills/edgeflow-adapter-developer/SKILL.md +++ b/.agents/skills/edgeflow-adapter-developer/SKILL.md @@ -36,8 +36,8 @@ description: 新增或修改 LLM-EdgeFlow Adapter 的业务输入输出、InputC 字符串通过 `OutputStringWriter` 按实际容量和长度写入,不保存借用指针。 请求字段解析和最终协议组装留在 Converter;不向 Operator 中央分发添加业务分支, -也不把这些操作放进 Demo、Node 或 Core。同一 biz 的多个 Binding 必须保持外部 schema、 -载体与 slot 契约一致。Binding 的批次限制及池深收紧由共享 Integration 规则处理。 +也不把这些操作放进 Demo、Node 或 Core。每个 biz 只注册一个 Binding;外部契约不同就是另一个 +biz。Binding 的批次限制及池深收紧由共享 Integration 规则处理。 若需 Demo,只补载体、调用与展示注册,不复制业务转换。 ## 验证新增路径 diff --git a/.agents/skills/llm-edgeflow-developer-guide/references/integration.md b/.agents/skills/llm-edgeflow-developer-guide/references/integration.md index c6628597..33576b84 100644 --- a/.agents/skills/llm-edgeflow-developer-guide/references/integration.md +++ b/.agents/skills/llm-edgeflow-developer-guide/references/integration.md @@ -25,7 +25,7 @@ slot configurations, allocator and capacities. It is not a Pipeline Node and doe 2. Operator public API lives in `include/edgeflow/operator/interface.h`; `types.h` forwards platform data structures. Platform mock interaction types live in `include/platform_mock/operator_types.h`, and payload structures in `operator_data_types.h`; see that directory's README for the distinction from real company headers. 3. Preserve exported Operator functions and their exception barrier in `src/adapter/operator/operator_adapter.cpp`: `noexcept`, `try`, `catch (const std::exception&)`, and `catch (...)`. 4. Implement ordinary `DecodeInputFn` callbacks in `InputConverterDefinition` under `src/adapter/input/`, `EncodeOutputFn` callbacks in `OutputConverterDefinition` under `src/adapter/output/`, and business binding through `IoBindingDefinition` under `src/adapter/biz/`. Register through `REGISTER_INPUT_CONVERTER`, `REGISTER_OUTPUT_CONVERTER`, and `REGISTER_IO_BINDING`. -5. Register `BizDefinition` with `PipelineCatalog::RegisterBizDefinition` to declare `biz_name` and complete ingress/egress Blackboard ports. `IoBindingDefinition` selects converters and defaults to the standard batch bound of 64; converter logical ports map to the same-named keys unless the binding lists a renamed mapping. External Pipeline JSON requires `deployment.io.io_binding` and rejects root `biz_name`; Integration derives the internal business boundary from the selected registration. Demo resolves its runner through the SDK configuration query; neither CLI nor Profile accepts a business selector. Bindings of the same biz must declare the same external schema, carrier and slot contract; Registry audit and deployment preflight enforce this before any payload cast. +5. Register `BizDefinition` with `PipelineCatalog::RegisterBizDefinition` to declare `biz_name` and complete ingress/egress Blackboard ports. `IoBindingDefinition` selects converters and defaults to the standard batch bound of 64; converter logical port names are the biz Blackboard keys. External Pipeline JSON requires `deployment.io.io_binding` and rejects root `biz_name`; Integration derives the internal business boundary from the selected registration. Demo resolves its runner through the SDK configuration query; neither CLI nor Profile accepts a business selector. Each biz registers exactly one binding, so the biz identifies one external contract; a second binding for the same biz is a registry conflict that fails SDK initialization. 6. Copy input data when the lifetime requires it, store request-scoped values in `AlgContext`, and pack output into leased pool slots only through the documented ownership contract. Bindings default to the framework standard batch bound of 64; override @@ -39,7 +39,7 @@ Process batch limit at the output pool depth; a larger binding limit cannot rela For one required host slot, one business payload stream and one payload/result per request, use `DecodeRequestRows` / `EncodeResultRows` from `converter_authoring.h`. Business callbacks handle one owned payload or one borrowed output row; -helpers own looping, bindings, provenance, request IDs and diagnostic location. `OutputStringWriter` +helpers own looping, provenance, request IDs and diagnostic location. `OutputStringWriter` uses actual pool capacities and explicit string lengths. Do not retain its borrowed view or pointers. Independent metadata streams, multi-slot, expanded and aggregated conversions keep their explicit algorithms and existing lower-level helpers; a single external slot alone does not imply a single stream. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c1e97cc..7de915d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,9 +69,10 @@ its last stage merges; delete the plan then. A plan describes intended changes, Until the first production release there are no compatibility consumers. Rename or remove identifiers, fields, parameters and diagnostic codes in place and update every reference in the -same change; do not keep aliases, retired values or migration shims. After release, an -incompatible external contract change adds a new versioned IoBinding instead of changing an -existing ID. +same change; do not keep aliases, retired values or migration shims. Each biz has exactly one +IoBinding and therefore one external contract. After release, an incompatible external contract +change is made in place and shipped in a new SDK release; add a new biz only when the old and new +contracts must be served side by side. Use framework defaults for platform limits and scheduling values when a conservative value is correct for every supported scenario, even if slower, or an independent runtime hard diff --git a/configs/README.md b/configs/README.md index f0a42d70..c99f980d 100644 --- a/configs/README.md +++ b/configs/README.md @@ -50,7 +50,7 @@ Validator 从输入数据的唯一生产者推导依赖,`depends_on` 仅用于 每份 Pipeline 显式填写 `deployment.io.io_binding`,它决定外部 C 结构体与内部数据的转换契约。 框架从注册关系获得业务边界、输入/输出转换器、端口和输出类型,不靠文件名或后缀猜测。 Demo 根据配置自动选取运行入口,Profile 只保存配置路径、数据集和执行参数。 -同一业务的多个 binding 必须声明一致的外部协议、载体和有效槽位,注册审计及部署预检都会校验。 +每个业务只注册一个 binding;同一业务的第二个 binding 会被注册审计拒绝,SDK 初始化失败。 ```json { diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 76242e57..d6b42dea 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +每个业务只注册一个 IoBinding:同一 `biz_name` 的第二个 binding 在注册时被拒绝并记为注册冲突 +(SDK 初始化返回 -6),删除同业务多 binding 的外部契约比对与部署诊断 `BIZ_IO_CONTRACT_MISMATCH`。 +发布后外部契约不兼容的变化原地修改并随新 SDK 版本发布,新旧契约须并存时新增业务,不再新增带版本的 binding。 + 转换器回调去掉 `InputPortBindings` / `OutputPortBindings` 参数:`DecodeInputFn` / `EncodeOutputFn`、 `DecodeRequestRows`、`EncodeResultRows` 与 `ReadOutputValue` 直接使用端口常量读写 `AlgContext`。 写入未声明端口不再被静默发布到空键名。 @@ -17,8 +21,7 @@ Converter 定义精简(不涉及 Operator ABI、Pipeline JSON 与 `.conf`) `schema_version`、`external_type`、输出 `cardinality` 与 `capacity_policy`,以及槽位的 `value_type` 与 `capacity_fields`。输出槽容量字段只由 ValueType 决定,`ExternalInputSlot` / `ExternalOutputSlot` 只接受槽名。Catalog 的 Converter 不再导出上述元数据,`external_type` 改由槽位类型推导。 -`schema_id` 保留为外部协议 ID:载体相同的 Converter 可能解析语义不同,同业务 binding 的一致性检查 -继续比较它。 +`schema_id` 保留为外部协议 ID:载体相同的 Converter 可能解析语义不同。 开发工具修复:Markdown 链接检查支持单引号与圆括号标题、带空格的尖括号目标及平衡或转义的 目标圆括号;生产版 `alg_pipeline_tool edit` 根据 `validation.diagnostics` 提示构建变体与测试工具。 diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 520a33da..46741e55 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -208,8 +208,8 @@ ValueType。宿主类型的转换都留在接入适配层,Node、Model 和 Bac 已有契约的新方案直接沿用对应 Demo 和数据集格式,只准备 Pipeline 与指向它的 `.conf`。 新契约先检查已有 Demo 运行代码是否支持所需载体、槽位及数据集格式,可满足时复用这些代码。 Pipeline 只选择 `io_binding`,Demo 调用 `ResolveOperatorConfigBiz` 解析业务身份并选择 runner。 -Profile 不填写业务名。SDK 预检与注册审计共用同业务 binding 的外部协议(Converter 的 `schema_id`)、载体及槽位一致性检查。Demo 按该契约准备载体, -不能仅因宿主类型相同就复用另一业务契约;同一业务也不能登记载体或协议不兼容的 binding。 +Profile 不填写业务名。每个业务只注册一个 binding,业务名唯一确定外部协议、载体及槽位。Demo 按该契约准备载体, +不能仅因宿主类型相同就复用另一业务契约;外部协议不同的输入或输出属于另一个业务。 这里的数据转换仅指载体构造和结果展示,外部协议的解包、 字段选择与响应组装仍在转换器;不得把原始业务请求预先拆成内部节点输入。 新绑定需要接入统一 Demo 时,按 `keyword_match_demo.cpp` 补齐以下部分: diff --git a/doc/dev_guide/source_layout.md b/doc/dev_guide/source_layout.md index 02b62b68..8a91700b 100644 --- a/doc/dev_guide/source_layout.md +++ b/doc/dev_guide/source_layout.md @@ -156,7 +156,7 @@ src/engine/ | 位置 | 形式 | 示例 | | --- | --- | --- | | `biz_name`、`REGISTER_DEMO_BIZ` | `<词根>`,不带版本号 | `dialogue_audit` | -| `binding_id` | `<词根>.operator.v`;外部契约不兼容变化时新增版本 | `dialogue_audit.operator.v1` | +| `binding_id` | `<词根>.operator.v1`,每个业务一个 | `dialogue_audit.operator.v1` | | 绑定源码与 Demo 源码 | `src/adapter/biz/<词根>_bindings.cpp`、`demo/biz/<词根>_demo.cpp` | `dialogue_audit_bindings.cpp` | | 方案、数据集与 Profile | `pipeline_<词根>_<变体>`、`corpus_<词根>`、`<词根>_<变体>` | `pipeline_dialogue_audit_kite.json` | | 中文名 | `BizDefinition.display_name`,Demo 标题使用同一名称 | 对话合规审核 | diff --git a/include/adapter/io_binding_registry.h b/include/adapter/io_binding_registry.h index bb915bee..eb49d38a 100644 --- a/include/adapter/io_binding_registry.h +++ b/include/adapter/io_binding_registry.h @@ -31,10 +31,6 @@ class IoBindingRegistry { std::vector AllBindings() const; - // 一个 biz 通过其全部 binding 标识一份完整的外部契约。 - bool ValidateBizContract(const std::string& biz_name, - std::string* error = nullptr) const; - bool HasConflict() const; std::vector GetConflictErrors() const; diff --git a/include/adapter/io_converter.h b/include/adapter/io_converter.h index b2d43fbd..8dc8dd18 100644 --- a/include/adapter/io_converter.h +++ b/include/adapter/io_converter.h @@ -200,7 +200,7 @@ using EncodeOutputFn = int (*)(AlgContext* context, struct InputConverterDefinition { std::string converter_id; - // 外部载荷协议 ID。载体相同时,同一 biz 的多个 Binding 依靠它区分解析语义。 + // 外部载荷协议 ID,由 Catalog 导出;载体相同的 Converter 可按不同协议解析。 std::string schema_id; std::vector external_slots; std::vector logical_ports; // 发布的内部逻辑输出端口 diff --git a/include/edgeflow/operator/interface.h b/include/edgeflow/operator/interface.h index 92569c34..83e647a6 100644 --- a/include/edgeflow/operator/interface.h +++ b/include/edgeflow/operator/interface.h @@ -60,7 +60,7 @@ COMPANY_ALG_API const char* GetOperatorLastError() noexcept; /** * @brief 解析部署配置并返回由 I/O 绑定确定的业务契约名。 - * 只读预检,不加载模型、不执行转换;同业务的绑定须有一致的外部契约。 + * 只读预检,不加载模型、不执行转换;每个业务只有一个绑定。 * @param out_biz_name 必需的结果指针;失败时为空,成功时为完整业务名。 * @return 0 成功,-2 参数/配置错误,其他负值为验证或内部异常。 */ diff --git a/src/adapter/deployment_preparation.cpp b/src/adapter/deployment_preparation.cpp index 06630a9b..1d14ba1a 100644 --- a/src/adapter/deployment_preparation.cpp +++ b/src/adapter/deployment_preparation.cpp @@ -125,17 +125,6 @@ bool PrepareDeploymentDocument(const nlohmann::json& document, return false; } - std::string contract_error; - if (!IoBindingRegistry::Instance().ValidateBizContract(binding->biz_name, - &contract_error)) { - if (diagnostic) { - diagnostic->code = "BIZ_IO_CONTRACT_MISMATCH"; - diagnostic->path = "/deployment/io/io_binding"; - diagnostic->message = contract_error; - } - return false; - } - const size_t max_batch = EffectiveMaxBatchSize(*binding, *in_conv, *out_conv); if (max_batch == 0) { if (diagnostic) { diff --git a/src/adapter/io_binding_registry.cpp b/src/adapter/io_binding_registry.cpp index fee6da68..f043d81f 100644 --- a/src/adapter/io_binding_registry.cpp +++ b/src/adapter/io_binding_registry.cpp @@ -1,76 +1,12 @@ #include "adapter/io_binding_registry.h" #include -#include #include "adapter/io_converter_registry.h" #include "adapter/operator/operator_value_type_registry.h" #include "core/pipeline_catalog.h" namespace llm_edgeflow { -namespace { - -// 容量字段由 type_suffix 对应的 ValueType 决定,无需单独比较。 -bool SameExternalSlots(const std::vector& left, - const std::vector& right) { - const auto signature = [](const auto& slots) { - using Slot = - std::tuple; - std::vector result; - for (const auto& slot : slots) { - result.emplace_back(slot.KeySuffix(), slot.type_id, slot.type_suffix, - slot.direction, slot.required); - } - std::sort(result.begin(), result.end()); - return result; - }; - return signature(left) == signature(right); -} - -// 载体与槽位相同的 Converter 仍可能按不同协议解析,须同时比较 schema_id。 -template -bool SameExternalContract(const Converter& left, const Converter& right) { - return left.schema_id == right.schema_id && - SameExternalSlots(left.external_slots, right.external_slots); -} - -bool CheckBizContract(std::vector bindings, - const std::string& biz_name, std::string* error) { - std::sort(bindings.begin(), bindings.end(), [](const auto& a, const auto& b) { - return a.binding_id < b.binding_id; - }); - const auto& converters = IoConverterRegistry::Instance(); - const InputConverterDefinition* first_input = nullptr; - const OutputConverterDefinition* first_output = nullptr; - std::string first_binding; - for (const auto& binding : bindings) { - if (binding.biz_name != biz_name) continue; - const auto* input = - converters.FindInputConverter(binding.input_converter_id); - const auto* output = - converters.FindOutputConverter(binding.output_converter_id); - if (!input || !output) { - if (error) - *error = "Binding '" + binding.binding_id + - "' references an unregistered converter"; - return false; - } - if (first_input && (!SameExternalContract(*first_input, *input) || - !SameExternalContract(*first_output, *output))) { - if (error) - *error = "Bindings '" + first_binding + "' and '" + binding.binding_id + - "' for biz_name '" + biz_name + - "' declare different external I/O contracts"; - return false; - } - first_input = input; - first_output = output; - first_binding = binding.binding_id; - } - return true; -} - -} // namespace std::vector EffectiveCapacityFields( const ExternalSlotDefinition& slot) { @@ -130,6 +66,14 @@ bool IoBindingRegistry::RegisterBinding(const IoBindingDefinition& def) { conflicts_.Record("Duplicate IoBinding registration: " + def.binding_id); return false; } + // 一个业务只有一份外部契约,因此只登记一个 binding。 + for (const auto& [id, existing] : bindings_) { + if (existing.biz_name == def.biz_name) { + conflicts_.Record("Duplicate IoBinding for biz_name '" + def.biz_name + + "': " + id + ", " + def.binding_id); + return false; + } + } bindings_[def.binding_id] = def; return true; @@ -155,11 +99,6 @@ std::vector IoBindingRegistry::AllBindings() const { return result; } -bool IoBindingRegistry::ValidateBizContract(const std::string& biz_name, - std::string* error) const { - return CheckBizContract(AllBindings(), biz_name, error); -} - bool IoBindingRegistry::HasConflict() const { std::lock_guard lock(mutex_); return conflicts_.HasConflict(); @@ -182,21 +121,6 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { const auto catalog_snapshot = PipelineCatalog::Snapshot(); - std::vector all_bindings; - std::vector biz_names; - for (const auto& [id, binding] : bindings_) { - all_bindings.push_back(binding); - biz_names.push_back(binding.biz_name); - } - std::sort(biz_names.begin(), biz_names.end()); - biz_names.erase(std::unique(biz_names.begin(), biz_names.end()), - biz_names.end()); - for (const auto& biz_name : biz_names) { - std::string error; - if (!CheckBizContract(all_bindings, biz_name, &error)) - errors.push_back(error); - } - for (const auto& [binding_id, binding] : bindings_) { // 1. 检查 biz_name 是否在 PipelineCatalog 中已注册 const auto* biz_def = catalog_snapshot.FindBiz(binding.biz_name); diff --git a/tests/contract/abi/test_operator_safety.cpp b/tests/contract/abi/test_operator_safety.cpp index d052a370..63a7c1a8 100644 --- a/tests/contract/abi/test_operator_safety.cpp +++ b/tests/contract/abi/test_operator_safety.cpp @@ -477,6 +477,13 @@ TEST_F(OperatorSafetyTest, BindingBatchLimitOverridesLargerPoolAndConverters) { std::filesystem::remove_all(directory, error); } } cleanup{registry.AllBindings(), directory}; + // 每个业务只有一个 binding:用收紧批次的副本替换原 binding。 + registry.ClearForTesting(); + for (const auto& saved : cleanup.bindings) { + if (saved.biz_name != binding.biz_name) { + ASSERT_TRUE(registry.RegisterBinding(saved)); + } + } ASSERT_TRUE(registry.RegisterBinding(binding)); std::filesystem::create_directory(directory); std::ifstream source("configs/pipeline_keyword_match_rules.json"); diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 8b1babee..5787c22d 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -2537,8 +2537,15 @@ TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { const auto* nested = IoBindingRegistry::Instance().FindBinding( "nested_output_test.operator.v1"); ASSERT_NE(nested, nullptr); + // 每个业务只有一个 binding,测试 binding 使用复制出的独立业务。 + auto biz = *PipelineCatalog::FindBiz(nested->biz_name); + biz.biz_name = "test_request_id_count"; + if (!PipelineCatalog::FindBiz(biz.biz_name)) { + ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(biz)); + } auto binding = *nested; binding.binding_id = "request_id_count_test.operator.v1"; + binding.biz_name = biz.biz_name; binding.input_converter_id = input.converter_id; if (!IoBindingRegistry::Instance().FindBinding(binding.binding_id)) ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 38fb7ffb..5dc14422 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -963,16 +963,23 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { EXPECT_EQ((*sentences)[0].data, "keyword sentence"); } - // 跨业务复用证明:在测试专用绑定中复用 text.plain.operator.v1 + // 跨业务复用证明:在测试专用业务的绑定中复用 text.plain.operator.v1 { + auto reuse_biz = *PipelineCatalog::FindBiz("entity_extract"); + reuse_biz.biz_name = "test_purity_reuse"; + PipelineCatalog::RegisterBizDefinition(reuse_biz); IoBindingDefinition test_reuse_binding; test_reuse_binding.binding_id = "test_purity_reuse.operator.v1"; - test_reuse_binding.biz_name = "entity_extract"; + test_reuse_binding.biz_name = reuse_biz.biz_name; test_reuse_binding.input_converter_id = "text.plain.operator.v1"; test_reuse_binding.output_converter_id = "document.structured.operator.v1"; test_reuse_binding.max_batch_size = 64; - IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding); + if (!IoBindingRegistry::Instance().FindBinding( + test_reuse_binding.binding_id)) { + ASSERT_TRUE( + IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding)); + } const auto* b_test = IoBindingRegistry::Instance().FindBinding( "test_purity_reuse.operator.v1"); diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index ad5f1c5e..61de64d4 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -339,10 +339,13 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { EXPECT_TRUE(reg.Audit(&errors)); EXPECT_TRUE(errors.empty()); - // 2. 注册未被选择使用的非法绑定:输入 Converter 的端口不在业务契约中 + // 2. 为另一业务注册未被选用的非法绑定:输入端口不在业务契约中 + auto unselected_biz = *PipelineCatalog::FindBiz("test_biz_v1"); + unselected_biz.biz_name = "unselected_biz"; + ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(unselected_biz)); IoBindingDefinition illegal_binding; illegal_binding.binding_id = "unselected_bad.operator.v1"; - illegal_binding.biz_name = "test_biz_v1"; + illegal_binding.biz_name = unselected_biz.biz_name; illegal_binding.input_converter_id = RegisterUnmatchedInputConverter(); illegal_binding.output_converter_id = "test.out.operator"; @@ -565,36 +568,6 @@ TEST_F(IoBindingRegistryTest, StrictConfigDirectoryIsolationAndCwdInvariance) { fs::remove_all(root_dir); } -TEST_F(IoBindingRegistryTest, FailClosedAuditRejectsInvalidUnselectedBinding) { - auto& reg = IoBindingRegistry::Instance(); - - // 注册合法绑定 - IoBindingDefinition valid_binding; - valid_binding.binding_id = "test_biz.operator.v1"; - valid_binding.biz_name = "test_biz_v1"; - - valid_binding.input_converter_id = "test.in.operator"; - valid_binding.output_converter_id = "test.out.operator"; - EXPECT_TRUE(reg.RegisterBinding(valid_binding)); - - // 注册一个未被任何配置选中的非法绑定 (输入端口不覆盖必需入口) - IoBindingDefinition unselected_bad_binding; - unselected_bad_binding.binding_id = "unselected_bad.operator.v1"; - unselected_bad_binding.biz_name = "test_biz_v1"; - - unselected_bad_binding.input_converter_id = RegisterUnmatchedInputConverter(); - unselected_bad_binding.output_converter_id = "test.out.operator"; - EXPECT_TRUE(reg.RegisterBinding(unselected_bad_binding)); - - // 全量 Audit 必须对所有已注册绑定实行 Fail-Closed 检查 - std::vector audit_errors; - EXPECT_FALSE(reg.Audit(&audit_errors)); - EXPECT_FALSE(audit_errors.empty()); - - // GlobalInit 必须失败并返回 -6 (COMPANY_ALG_ERR_REGISTRY_CONFLICT) - EXPECT_EQ(SharedAlgorithmRuntime::GlobalInit(), -6); -} - TEST_F(IoBindingRegistryTest, SplitPipelineDocumentAndCoreBoundary) { RegisterTestBizBinding(); nlohmann::json document = { @@ -1011,10 +984,11 @@ TEST_F(IoBindingRegistryTest, OutputMemoryOverridesRemainOptional) { TEST_F(IoBindingRegistryTest, ExplicitBindingSelectsRegisteredBusinessWithoutNameInference) { - RegisterTestBizBinding(); - auto alternate = - *IoBindingRegistry::Instance().FindBinding("test_biz.operator.v1"); + IoBindingDefinition alternate; alternate.binding_id = "unrelated_name.for.explicit_selection"; + alternate.biz_name = "test_biz_v1"; + alternate.input_converter_id = "test.in.operator"; + alternate.output_converter_id = "test.out.operator"; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(alternate)); nlohmann::json document = { {"deployment", {{"io", {{"io_binding", alternate.binding_id}}}}}, @@ -1087,170 +1061,52 @@ TEST_F(IoBindingRegistryTest, DefaultsAndOverridesKeepOptionalOutputOptIn) { EXPECT_EQ(prepared.output_specs.at("second").type, "entity_out"); } -TEST_F(IoBindingRegistryTest, - SameSuffixDifferentCarrierIsRejectedBeforeConversion) { - const auto root = - fs::temp_directory_path() / "edgeflow_biz_contract_preflight"; - fs::create_directories(root); - for (bool incompatible_input : {true, false}) { - SCOPED_TRACE(incompatible_input ? "input carrier" : "output carrier"); - IoBindingRegistry::Instance().ClearForTesting(); - RegisterTestBizBinding(); - auto alternate = - *IoBindingRegistry::Instance().FindBinding("test_biz.operator.v1"); - alternate.binding_id = - incompatible_input ? "incompatible.input" : "incompatible.output"; - if (incompatible_input) { - auto converter = *IoConverterRegistry::Instance().FindInputConverter( - "test.in.operator"); - converter.converter_id = "incompatible.input.converter"; - auto& slot = converter.external_slots.front(); - slot.type_id = "CompanyOperatorKeywordInput"; - slot.type_suffix = "keyword_in"; - slot.key_suffix = "entity_in"; - ASSERT_EQ(slot.KeySuffix(), "entity_in"); - ASSERT_TRUE( - IoConverterRegistry::Instance().RegisterInputConverter(converter)); - alternate.input_converter_id = converter.converter_id; - } else { - auto converter = *IoConverterRegistry::Instance().FindOutputConverter( - "test.out.operator"); - converter.converter_id = "incompatible.output.converter"; - auto& slot = converter.external_slots.front(); - slot.type_id = "CompanyOperatorKeywordOutput"; - slot.type_suffix = "keyword_out"; - slot.key_suffix = "entity_out"; - ASSERT_EQ(slot.KeySuffix(), "entity_out"); - ASSERT_TRUE( - IoConverterRegistry::Instance().RegisterOutputConverter(converter)); - alternate.output_converter_id = converter.converter_id; - } - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(alternate)); - const int decoded_before = dummy_decode_calls; - const int encoded_before = dummy_encode_calls; - std::vector errors; - EXPECT_FALSE(IoBindingRegistry::Instance().Audit(&errors)); - ASSERT_EQ(errors.size(), 1U) << "Alternate carriers must pass the existing " - "registration and layout checks"; - EXPECT_TRUE( - std::any_of(errors.begin(), errors.end(), [](const auto& error) { - return error.find("different external I/O contracts") != - std::string::npos; - })); +TEST_F(IoBindingRegistryTest, SecondBindingForSameBizIsRejected) { + RegisterTestBizBinding(); + auto converter = + *IoConverterRegistry::Instance().FindOutputConverter("test.out.operator"); + converter.converter_id = "keyword.output.converter"; + auto& slot = converter.external_slots.front(); + slot.type_id = "CompanyOperatorKeywordOutput"; + slot.type_suffix = "keyword_out"; + slot.key_suffix = "entity_out"; + ASSERT_TRUE( + IoConverterRegistry::Instance().RegisterOutputConverter(converter)); + auto second = + *IoBindingRegistry::Instance().FindBinding("test_biz.operator.v1"); + second.binding_id = "second.binding"; + second.output_converter_id = converter.converter_id; - nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, - {"pipeline", DefaultPipelineNodes()}}; - PreparedDeployment prepared; - DeploymentDiagnostic diagnostic; - EXPECT_FALSE( - PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)); - EXPECT_EQ(diagnostic.code, "BIZ_IO_CONTRACT_MISMATCH"); - EXPECT_EQ(diagnostic.path, "/deployment/io/io_binding"); - EXPECT_EQ(prepared.input_converter, nullptr); - EXPECT_EQ(prepared.output_converter, nullptr); - document["deployment"]["io"]["io_binding"] = alternate.binding_id; - EXPECT_FALSE( - PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)); - EXPECT_EQ(diagnostic.code, "BIZ_IO_CONTRACT_MISMATCH"); - - // 公开预检必须在不执行 Init 的情况下强制载体契约。 - std::ofstream(root / "pipeline.json") << document; - std::ofstream(root / "pipeline.conf") - << nlohmann::json{{"pipe_path", "pipeline.json"}}; - char error[512]{}; - std::string resolved_biz = "stale"; - EXPECT_EQ(operator_api::ResolveOperatorConfigBiz( - root.string().c_str(), "pipeline.conf", &resolved_biz, error, - sizeof(error)), - -2); - EXPECT_TRUE(resolved_biz.empty()); - EXPECT_NE(std::string(error).find("different external I/O contracts"), - std::string::npos); - EXPECT_EQ(dummy_decode_calls, decoded_before); - EXPECT_EQ(dummy_encode_calls, encoded_before); - - // 其他业务有自己的载体契约,不受影响。 - auto independent = *PipelineCatalog::FindBiz("test_biz_v1"); - independent.biz_name = - incompatible_input ? "separate_input_biz" : "separate_output_biz"; - ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(independent)); - alternate.biz_name = independent.biz_name; - alternate.binding_id += ".separate"; - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(alternate)); - - document["deployment"]["io"]["io_binding"] = alternate.binding_id; - ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) - << diagnostic.message; - EXPECT_EQ(prepared.binding.biz_name, independent.biz_name); - EXPECT_EQ(prepared.output_specs.at("entity_out").type, - incompatible_input ? "entity_out" : "keyword_out"); - } - fs::remove_all(root); -} + // 同一业务只有一份外部契约:第二个 binding 不论载体是否兼容都在注册时被拒绝。 + EXPECT_FALSE(IoBindingRegistry::Instance().RegisterBinding(second)); + EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(second.binding_id), + nullptr); + std::vector errors; + EXPECT_FALSE(IoBindingRegistry::Instance().Audit(&errors)); + EXPECT_TRUE(std::any_of(errors.begin(), errors.end(), [](const auto& error) { + return error.find("Duplicate IoBinding for biz_name 'test_biz_v1'") != + std::string::npos; + })); + IoBindingRegistry::Instance().ResetConflictForTesting(); -TEST_F(IoBindingRegistryTest, - EquivalentCarriersAllowConverterOrderAndBatchChanges) { - auto input = - *IoConverterRegistry::Instance().FindInputConverter("test.in.operator"); - input.converter_id = "equivalent.input.first"; - auto input_aux = input.external_slots.front(); - input_aux.slot_name = "extra_input"; - input_aux.key_suffix = "extra_input"; - input_aux.required = false; - input.external_slots.push_back(input_aux); - auto output = - *IoConverterRegistry::Instance().FindOutputConverter("test.out.operator"); - output.converter_id = "equivalent.output.first"; - auto output_aux = output.external_slots.front(); - output_aux.slot_name = "audit"; - output_aux.key_suffix = "audit"; - output_aux.required = false; - output.external_slots.push_back(output_aux); - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterInputConverter(input)); - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterOutputConverter(output)); - IoBindingDefinition first; - first.binding_id = "equivalent.first"; - first.biz_name = "test_biz_v1"; - first.input_converter_id = input.converter_id; - first.output_converter_id = output.converter_id; - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(first)); - - input.converter_id = "equivalent.input.second"; - output.converter_id = "equivalent.output.second"; - input.max_batch_size = 8; - output.max_batch_size = 16; - std::reverse(input.external_slots.begin(), input.external_slots.end()); - std::reverse(output.external_slots.begin(), output.external_slots.end()); - for (auto& slot : input.external_slots) - slot.slot_name = "renamed_" + slot.slot_name; - for (auto& slot : output.external_slots) - slot.slot_name = "renamed_" + slot.slot_name; - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterInputConverter(input)); - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterOutputConverter(output)); - auto second = first; - second.binding_id = "equivalent.second"; - second.input_converter_id = input.converter_id; - second.output_converter_id = output.converter_id; + // 另一业务可以使用自己的载体契约。 + auto separate = *PipelineCatalog::FindBiz("test_biz_v1"); + separate.biz_name = "separate_output_biz"; + ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(separate)); + second.biz_name = separate.biz_name; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(second)); - std::vector errors; - EXPECT_TRUE(IoBindingRegistry::Instance().Audit(&errors)); - EXPECT_TRUE(errors.empty()); + EXPECT_TRUE(IoBindingRegistry::Instance().Audit(&errors)) + << (errors.empty() ? "" : errors.front()); - nlohmann::json document = {{"pipeline", DefaultPipelineNodes()}}; + nlohmann::json document = { + {"deployment", {{"io", {{"io_binding", second.binding_id}}}}}, + {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; - for (const auto& binding : {first, second}) { - SCOPED_TRACE(binding.binding_id); - document["deployment"]["io"]["io_binding"] = binding.binding_id; - ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) - << diagnostic.message; - EXPECT_EQ(prepared.binding.binding_id, binding.binding_id); - EXPECT_EQ(prepared.effective_max_batch_size, - binding.binding_id == first.binding_id ? 64U : 8U); - ASSERT_EQ(prepared.output_specs.size(), 1U); - EXPECT_EQ(prepared.output_specs.begin()->second.type, "entity_out"); - } + ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) + << diagnostic.message; + EXPECT_EQ(prepared.binding.biz_name, separate.biz_name); + EXPECT_EQ(prepared.output_specs.at("entity_out").type, "keyword_out"); } TEST_F(IoBindingRegistryTest, EffectiveBatchLimitIncludesBindingBound) { diff --git a/tests/unit/adapter/test_text_converters.cpp b/tests/unit/adapter/test_text_converters.cpp index 433b63a4..e8321a87 100644 --- a/tests/unit/adapter/test_text_converters.cpp +++ b/tests/unit/adapter/test_text_converters.cpp @@ -1,5 +1,6 @@ #include +#include #include #include "adapter/adapter_status.h" @@ -9,6 +10,7 @@ #include "adapter/io_converter_registry.h" #include "contracts/inference_payloads.h" #include "core/alg_context.h" +#include "core/pipeline_catalog.h" #include "edgeflow/operator/types.h" #include "platform_mock/operator_data_types.h" #include "tests/support/adapter_test_views.h" @@ -184,16 +186,23 @@ TEST_F(TextConvertersTest, ProductionBindingsUseDeclaredHostTypes) { } TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { - // 证明同一个转换器 ID 可以在不同绑定间复用:通过测试专用绑定 + // 证明同一个转换器 ID 可以在不同业务的绑定间复用:通过测试专用业务 + auto reuse_biz = *PipelineCatalog::FindBiz("entity_extract"); + reuse_biz.biz_name = "test_text_reuse"; + PipelineCatalog::RegisterBizDefinition(reuse_biz); IoBindingDefinition test_reuse_binding; test_reuse_binding.binding_id = "test_text_reuse.operator.v1"; - test_reuse_binding.biz_name = "entity_extract"; + test_reuse_binding.biz_name = reuse_biz.biz_name; test_reuse_binding.input_converter_id = "text.plain.operator.v1"; test_reuse_binding.output_converter_id = "document.structured.operator.v1"; test_reuse_binding.max_batch_size = 64; - IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding); + if (!IoBindingRegistry::Instance().FindBinding( + test_reuse_binding.binding_id)) { + ASSERT_TRUE( + IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding)); + } const auto* b1 = IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); @@ -233,21 +242,20 @@ TEST_F(TextConvertersTest, SameCarrierDifferentPayloadBindingIsRejected) { ASSERT_EQ(decode("translate.json.operator.v1", request), "hello"); ASSERT_EQ(decode("text.plain.operator.v1", request), request); + // 同一业务只登记一个 binding:translate 不能再挂另一协议的 Converter。 auto& bindings = IoBindingRegistry::Instance(); - const auto saved = bindings.AllBindings(); auto alternate = *bindings.FindBinding("translate.operator.v1"); alternate.binding_id = "translate.plain_text_input"; alternate.input_converter_id = "text.plain.operator.v1"; - ASSERT_TRUE(bindings.RegisterBinding(alternate)); - - std::string error; - EXPECT_FALSE(bindings.ValidateBizContract("translate", &error)); - EXPECT_NE(error.find("different external I/O contracts"), std::string::npos); + EXPECT_FALSE(bindings.RegisterBinding(alternate)); + EXPECT_EQ(bindings.FindBinding(alternate.binding_id), nullptr); std::vector errors; EXPECT_FALSE(bindings.Audit(&errors)); - - bindings.ClearForTesting(); - for (const auto& binding : saved) bindings.RegisterBinding(binding); + EXPECT_TRUE(std::any_of(errors.begin(), errors.end(), [](const auto& error) { + return error.find("Duplicate IoBinding for biz_name 'translate'") != + std::string::npos; + })); + bindings.ResetConflictForTesting(); } TEST_F(TextConvertersTest, MissingResultsDifferFromOutputCapacityFailures) { From 83db7419787bcb838bd45b98dc7b51504b576a39 Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 17:00:04 +0800 Subject: [PATCH 2/4] refactor(adapter): identify IoBindings by biz name and drop ID versions With one binding per biz, binding_id only repeated the biz name with a fixed ".operator.v1" suffix. IoBindingDefinition loses binding_id and the registry is keyed by biz_name: Pipeline deployment.io.io_binding and the CLI --io-binding flag now take the biz name, and a duplicate biz registration is the same conflict as before. Converter IDs drop the ".operator.v1" suffix (text.plain.operator.v1 -> text.plain). Catalog io_bindings and validate-io no longer report binding_id; Studio selects bindings by biz_name. All Pipeline configs, Demo fixtures, tests and docs are updated in place (pre-release, no aliases). The naming table drops the binding_id row. OperatorSafetyTest's unknown/missing binding cases used a long-removed .conf format and failed for that reason only; they now use a valid .conf with a passing control case, and the obsolete cabi-name checks are removed. Co-Authored-By: Claude Opus 5.5 --- .../edgeflow-adapter-developer/SKILL.md | 2 +- .../skills/edgeflow-solution-planner/SKILL.md | 2 +- .../pipeline-composer/references/workflow.md | 8 +- README.md | 2 +- configs/README.md | 4 +- configs/pipeline_audio_asr_intent_cpu.json | 2 +- configs/pipeline_cross_rerank_cpu.json | 2 +- configs/pipeline_dialogue_audit_default.json | 2 +- configs/pipeline_dialogue_audit_kite.json | 2 +- configs/pipeline_doc_qa_cpu.json | 2 +- configs/pipeline_doc_qa_default.json | 2 +- configs/pipeline_doc_qa_kite.json | 2 +- ...line_doc_qa_kite_generated_embeddings.json | 2 +- configs/pipeline_doc_qa_rerank_cpu.json | 2 +- configs/pipeline_doc_qa_rerank_default.json | 2 +- configs/pipeline_doc_qa_rerank_kite.json | 2 +- configs/pipeline_entity_extract_cpu.json | 2 +- configs/pipeline_entity_extract_default.json | 2 +- configs/pipeline_entity_extract_kite.json | 2 +- configs/pipeline_keyword_match_rules.json | 2 +- configs/pipeline_ocr_invoice_qa_kite.json | 2 +- configs/pipeline_translate_cpu.json | 2 +- .../mock/pipeline_audio_asr_intent.json | 2 +- .../mock/pipeline_dialogue_audit.json | 2 +- demo/fixtures/mock/pipeline_doc_qa.json | 2 +- .../fixtures/mock/pipeline_doc_qa_custom.json | 2 +- .../fixtures/mock/pipeline_doc_qa_rerank.json | 2 +- .../mock/pipeline_entity_extract.json | 2 +- .../mock/pipeline_entity_extract_custom.json | 2 +- .../mock/pipeline_ocr_invoice_qa.json | 2 +- demo/fixtures/mock/pipeline_translate.json | 2 +- doc/CHANGELOG.md | 13 +- doc/architecture.md | 4 +- doc/dev_guide/business_onboarding.md | 26 +-- doc/dev_guide/first_control.md | 2 +- doc/dev_guide/first_custom_node.md | 2 +- doc/dev_guide/operator_output_allocation.md | 2 +- doc/dev_guide/source_layout.md | 14 +- doc/developer_guide.md | 2 +- doc/solutions/translate.md | 4 +- include/adapter/io_binding.h | 3 +- include/adapter/io_binding_registry.h | 6 +- plans/FRAMEWORK_SIMPLIFICATION_PLAN.md | 2 +- src/adapter/biz/README.md | 2 +- src/adapter/biz/audio_asr_intent_bindings.cpp | 6 +- src/adapter/biz/cross_rerank_bindings.cpp | 6 +- src/adapter/biz/dialogue_audit_bindings.cpp | 6 +- src/adapter/biz/doc_qa_bindings.cpp | 6 +- src/adapter/biz/entity_extract_bindings.cpp | 6 +- src/adapter/biz/keyword_match_bindings.cpp | 6 +- src/adapter/biz/ocr_invoice_qa_bindings.cpp | 6 +- src/adapter/biz/translate_bindings.cpp | 6 +- src/adapter/deployment_preparation.cpp | 8 +- src/adapter/input/audio_input.cpp | 2 +- src/adapter/input/audit_input.cpp | 2 +- src/adapter/input/doc_query_input.cpp | 2 +- src/adapter/input/image_query_input.cpp | 2 +- src/adapter/input/rerank_input.cpp | 2 +- src/adapter/input/text_input.cpp | 4 +- src/adapter/input/translate_json_input.cpp | 2 +- src/adapter/io_binding_registry.cpp | 68 +++---- src/adapter/io_catalog.cpp | 5 +- src/adapter/output/audio_result_output.cpp | 2 +- src/adapter/output/audit_result_output.cpp | 2 +- src/adapter/output/doc_answer_output.cpp | 2 +- src/adapter/output/invoice_result_output.cpp | 2 +- src/adapter/output/keyword_result_output.cpp | 2 +- src/adapter/output/rerank_result_output.cpp | 2 +- .../output/structured_document_output.cpp | 2 +- .../output/translation_json_output.cpp | 2 +- src/adapter/pipeline_document.cpp | 4 +- src/cli/alg_pipeline_tool.cpp | 25 ++- src/cli/pipeline_json_schema.cpp | 2 +- src/custom_nodes/README.md | 2 +- tests/RuntimeTests.cmake | 2 +- .../abi/test_adapter_contract_security.cpp | 134 ++++++------- tests/contract/abi/test_operator_safety.cpp | 80 ++++---- .../catalog/test_catalog_contract_ssot.cpp | 10 +- tests/fixtures/effects/entity_mock_exact.json | 2 +- tests/fixtures/effects/keyword_exact.json | 2 +- .../pipeline_cross_rerank_fixture.json | 2 +- .../pipeline_cross_rerank_missing_model.json | 2 +- .../operator/test_operator_api.cpp | 42 ++-- .../test_pipeline_catalog_validator.cpp | 34 ++-- tests/support/control_test_utils.h | 2 +- tests/tooling/generate_scaffold_fixtures.py | 2 +- tests/tooling/studio_browser_test.mjs | 20 +- tests/tooling/studio_fix_workflow_test.mjs | 2 +- tests/tooling/studio_graph_test.mjs | 4 +- tests/tooling/test_dev_recipe.py | 4 +- tests/tooling/test_pipeline_studio.py | 111 +++++------ tests/unit/adapter/test_adapter_purity.cpp | 185 +++++++++--------- .../unit/adapter/test_complex_converters.cpp | 42 ++-- .../unit/adapter/test_io_binding_registry.cpp | 153 +++++++-------- tests/unit/adapter/test_io_converters.cpp | 12 +- tests/unit/adapter/test_text_converters.cpp | 61 +++--- .../operator/test_operator_value_registry.cpp | 8 +- tools/pipeline_studio/README.md | 8 +- tools/pipeline_studio/web/app.js | 5 +- tools/pipeline_studio/web/workbench.js | 2 +- 100 files changed, 602 insertions(+), 675 deletions(-) diff --git a/.agents/skills/edgeflow-adapter-developer/SKILL.md b/.agents/skills/edgeflow-adapter-developer/SKILL.md index 689ab7f7..be03162f 100644 --- a/.agents/skills/edgeflow-adapter-developer/SKILL.md +++ b/.agents/skills/edgeflow-adapter-developer/SKILL.md @@ -50,7 +50,7 @@ biz。Binding 的批次限制及池深收紧由共享 Integration 规则处理 ```bash cmake --build build --target edgeflow_test_adapter_runner alg_pipeline_tool -j 4 ./build/edgeflow_test_adapter_runner --gtest_list_tests -./build/alg_pipeline_tool catalog --io-binding +./build/alg_pipeline_tool catalog --io-binding ``` 按改动运行列出的实际测试过滤器;业务契约测试须直接调用 Operator `Process`。 diff --git a/.agents/skills/edgeflow-solution-planner/SKILL.md b/.agents/skills/edgeflow-solution-planner/SKILL.md index 50d8946e..6293fb19 100644 --- a/.agents/skills/edgeflow-solution-planner/SKILL.md +++ b/.agents/skills/edgeflow-solution-planner/SKILL.md @@ -25,7 +25,7 @@ Catalog 和完整 Operator 请求/响应为依据;流程与设计边界遵循 ```bash ./build/alg_pipeline_tool catalog -./build/alg_pipeline_tool catalog --io-binding +./build/alg_pipeline_tool catalog --io-binding ./build/alg_pipeline_tool describe-node ./build/alg_pipeline_tool describe-model ./build/alg_pipeline_tool describe-backend diff --git a/.agents/skills/pipeline-composer/references/workflow.md b/.agents/skills/pipeline-composer/references/workflow.md index 5f36e510..cb30d00b 100644 --- a/.agents/skills/pipeline-composer/references/workflow.md +++ b/.agents/skills/pipeline-composer/references/workflow.md @@ -16,7 +16,7 @@ Build the tool if unavailable/stale, and rebuild after registration changes. Que biz contract and its filtered assets: ```bash -./build/alg_pipeline_tool catalog --io-binding +./build/alg_pipeline_tool catalog --io-binding ``` Use the production tool for the target build. For fixtures deliberately using test-only @@ -38,8 +38,8 @@ For an existing solution, edit the requested files instead of initializing anoth Reuse registered nodes; cloning a Pipeline does not retarget the source Profile. ```bash -./build/alg_pipeline_tool init --io-binding --profile -./build/alg_pipeline_tool init --io-binding --empty +./build/alg_pipeline_tool init --io-binding --profile +./build/alg_pipeline_tool init --io-binding --empty ``` `init` normally returns a versioned response containing `pipeline`. To save a @@ -47,7 +47,7 @@ runtime document directly, use `--raw` and a new destination (do not overwrite an existing solution): ```bash -./build/alg_pipeline_tool init --io-binding --profile --raw > +./build/alg_pipeline_tool init --io-binding --profile --raw > ``` Check the command's exit status before using the file, then validate the saved diff --git a/README.md b/README.md index 3de2643c..8b1ab120 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ cmake --build build --target alg_sdk alg_demo alg_pipeline_tool alg_show --paral ```bash # 查询当前构建中可用的业务契约与节点 -./build/alg_pipeline_tool catalog --io-binding keyword_match.operator.v1 +./build/alg_pipeline_tool catalog --io-binding keyword_match ./build/alg_pipeline_tool describe-node TextRuleMatchNode # 查看经过校验的执行计划 diff --git a/configs/README.md b/configs/README.md index c99f980d..6f89bf48 100644 --- a/configs/README.md +++ b/configs/README.md @@ -47,7 +47,7 @@ Validator 从输入数据的唯一生产者推导依赖,`depends_on` 仅用于 ## 业务入口与输出配置 -每份 Pipeline 显式填写 `deployment.io.io_binding`,它决定外部 C 结构体与内部数据的转换契约。 +每份 Pipeline 显式在 `deployment.io.io_binding` 填写业务名,它选择该业务唯一的绑定,决定外部 C 结构体与内部数据的转换契约。 框架从注册关系获得业务边界、输入/输出转换器、端口和输出类型,不靠文件名或后缀猜测。 Demo 根据配置自动选取运行入口,Profile 只保存配置路径、数据集和执行参数。 每个业务只注册一个 binding;同一业务的第二个 binding 会被注册审计拒绝,SDK 初始化失败。 @@ -56,7 +56,7 @@ Demo 根据配置自动选取运行入口,Profile 只保存配置路径、数 { "deployment": { "io": { - "io_binding": "keyword_match.operator.v1" + "io_binding": "keyword_match" } } } diff --git a/configs/pipeline_audio_asr_intent_cpu.json b/configs/pipeline_audio_asr_intent_cpu.json index 2a1a05a0..1b26892a 100644 --- a/configs/pipeline_audio_asr_intent_cpu.json +++ b/configs/pipeline_audio_asr_intent_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "audio_asr_intent.operator.v1" + "io_binding": "audio_asr_intent" } }, "models": [ diff --git a/configs/pipeline_cross_rerank_cpu.json b/configs/pipeline_cross_rerank_cpu.json index dbba27c7..4c99ea3e 100644 --- a/configs/pipeline_cross_rerank_cpu.json +++ b/configs/pipeline_cross_rerank_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "cross_rerank.operator.v1" + "io_binding": "cross_rerank" } }, "models": [ diff --git a/configs/pipeline_dialogue_audit_default.json b/configs/pipeline_dialogue_audit_default.json index 173cba0c..05c5f4a1 100644 --- a/configs/pipeline_dialogue_audit_default.json +++ b/configs/pipeline_dialogue_audit_default.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "dialogue_audit.operator.v1" + "io_binding": "dialogue_audit" } }, "models": [ diff --git a/configs/pipeline_dialogue_audit_kite.json b/configs/pipeline_dialogue_audit_kite.json index 492da546..013a9271 100644 --- a/configs/pipeline_dialogue_audit_kite.json +++ b/configs/pipeline_dialogue_audit_kite.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "dialogue_audit.operator.v1" + "io_binding": "dialogue_audit" } }, "models": [ diff --git a/configs/pipeline_doc_qa_cpu.json b/configs/pipeline_doc_qa_cpu.json index 94c2833d..1621faec 100644 --- a/configs/pipeline_doc_qa_cpu.json +++ b/configs/pipeline_doc_qa_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_default.json b/configs/pipeline_doc_qa_default.json index 6e390c09..173f1423 100644 --- a/configs/pipeline_doc_qa_default.json +++ b/configs/pipeline_doc_qa_default.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_kite.json b/configs/pipeline_doc_qa_kite.json index 7d6c9d90..264b9ce5 100644 --- a/configs/pipeline_doc_qa_kite.json +++ b/configs/pipeline_doc_qa_kite.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_kite_generated_embeddings.json b/configs/pipeline_doc_qa_kite_generated_embeddings.json index 20a49419..8af2b934 100644 --- a/configs/pipeline_doc_qa_kite_generated_embeddings.json +++ b/configs/pipeline_doc_qa_kite_generated_embeddings.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_rerank_cpu.json b/configs/pipeline_doc_qa_rerank_cpu.json index 68c20b4a..a497e059 100644 --- a/configs/pipeline_doc_qa_rerank_cpu.json +++ b/configs/pipeline_doc_qa_rerank_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_rerank_default.json b/configs/pipeline_doc_qa_rerank_default.json index bbd6cd53..31823f26 100644 --- a/configs/pipeline_doc_qa_rerank_default.json +++ b/configs/pipeline_doc_qa_rerank_default.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_doc_qa_rerank_kite.json b/configs/pipeline_doc_qa_rerank_kite.json index f0a20501..e5005e28 100644 --- a/configs/pipeline_doc_qa_rerank_kite.json +++ b/configs/pipeline_doc_qa_rerank_kite.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/configs/pipeline_entity_extract_cpu.json b/configs/pipeline_entity_extract_cpu.json index dfc28619..ac26b207 100644 --- a/configs/pipeline_entity_extract_cpu.json +++ b/configs/pipeline_entity_extract_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "entity_extract.operator.v1" + "io_binding": "entity_extract" } }, "models": [ diff --git a/configs/pipeline_entity_extract_default.json b/configs/pipeline_entity_extract_default.json index b5fabccf..90bb9997 100644 --- a/configs/pipeline_entity_extract_default.json +++ b/configs/pipeline_entity_extract_default.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "entity_extract.operator.v1" + "io_binding": "entity_extract" } }, "models": [ diff --git a/configs/pipeline_entity_extract_kite.json b/configs/pipeline_entity_extract_kite.json index a4408005..169d958f 100644 --- a/configs/pipeline_entity_extract_kite.json +++ b/configs/pipeline_entity_extract_kite.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "entity_extract.operator.v1" + "io_binding": "entity_extract" } }, "models": [ diff --git a/configs/pipeline_keyword_match_rules.json b/configs/pipeline_keyword_match_rules.json index c3348b32..d0c0e442 100644 --- a/configs/pipeline_keyword_match_rules.json +++ b/configs/pipeline_keyword_match_rules.json @@ -22,7 +22,7 @@ ], "deployment": { "io": { - "io_binding": "keyword_match.operator.v1" + "io_binding": "keyword_match" } } } diff --git a/configs/pipeline_ocr_invoice_qa_kite.json b/configs/pipeline_ocr_invoice_qa_kite.json index 2a2d177a..89925688 100644 --- a/configs/pipeline_ocr_invoice_qa_kite.json +++ b/configs/pipeline_ocr_invoice_qa_kite.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "ocr_invoice_qa.operator.v1" + "io_binding": "ocr_invoice_qa" } }, "models": [ diff --git a/configs/pipeline_translate_cpu.json b/configs/pipeline_translate_cpu.json index b5a2daf0..c77e0b28 100644 --- a/configs/pipeline_translate_cpu.json +++ b/configs/pipeline_translate_cpu.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "translate.operator.v1", + "io_binding": "translate", "out_mem": { "entity_out": { "capacities": { diff --git a/demo/fixtures/mock/pipeline_audio_asr_intent.json b/demo/fixtures/mock/pipeline_audio_asr_intent.json index 2143bccb..798cd846 100644 --- a/demo/fixtures/mock/pipeline_audio_asr_intent.json +++ b/demo/fixtures/mock/pipeline_audio_asr_intent.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "audio_asr_intent.operator.v1" + "io_binding": "audio_asr_intent" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_dialogue_audit.json b/demo/fixtures/mock/pipeline_dialogue_audit.json index e9ffa16c..a525f923 100644 --- a/demo/fixtures/mock/pipeline_dialogue_audit.json +++ b/demo/fixtures/mock/pipeline_dialogue_audit.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "dialogue_audit.operator.v1" + "io_binding": "dialogue_audit" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_doc_qa.json b/demo/fixtures/mock/pipeline_doc_qa.json index adad40f0..f61a6db1 100644 --- a/demo/fixtures/mock/pipeline_doc_qa.json +++ b/demo/fixtures/mock/pipeline_doc_qa.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_doc_qa_custom.json b/demo/fixtures/mock/pipeline_doc_qa_custom.json index 13fc2178..3d8d66db 100644 --- a/demo/fixtures/mock/pipeline_doc_qa_custom.json +++ b/demo/fixtures/mock/pipeline_doc_qa_custom.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_doc_qa_rerank.json b/demo/fixtures/mock/pipeline_doc_qa_rerank.json index b5a52c2d..39df66d0 100644 --- a/demo/fixtures/mock/pipeline_doc_qa_rerank.json +++ b/demo/fixtures/mock/pipeline_doc_qa_rerank.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "doc_qa.operator.v1" + "io_binding": "doc_qa" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_entity_extract.json b/demo/fixtures/mock/pipeline_entity_extract.json index b2046abd..3421e05e 100644 --- a/demo/fixtures/mock/pipeline_entity_extract.json +++ b/demo/fixtures/mock/pipeline_entity_extract.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "entity_extract.operator.v1" + "io_binding": "entity_extract" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_entity_extract_custom.json b/demo/fixtures/mock/pipeline_entity_extract_custom.json index 55e18f99..ac0c56f3 100644 --- a/demo/fixtures/mock/pipeline_entity_extract_custom.json +++ b/demo/fixtures/mock/pipeline_entity_extract_custom.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "entity_extract.operator.v1" + "io_binding": "entity_extract" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_ocr_invoice_qa.json b/demo/fixtures/mock/pipeline_ocr_invoice_qa.json index bf951974..833b778a 100644 --- a/demo/fixtures/mock/pipeline_ocr_invoice_qa.json +++ b/demo/fixtures/mock/pipeline_ocr_invoice_qa.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "ocr_invoice_qa.operator.v1" + "io_binding": "ocr_invoice_qa" } }, "models": [ diff --git a/demo/fixtures/mock/pipeline_translate.json b/demo/fixtures/mock/pipeline_translate.json index 0c050564..10f7cb76 100644 --- a/demo/fixtures/mock/pipeline_translate.json +++ b/demo/fixtures/mock/pipeline_translate.json @@ -31,7 +31,7 @@ ], "deployment": { "io": { - "io_binding": "translate.operator.v1" + "io_binding": "translate" } } } diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index d6b42dea..cf993a9a 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +IoBinding 改以业务名标识:删除 `IoBindingDefinition::binding_id`,Pipeline 的 `deployment.io.io_binding` +直接填写业务名(如 `keyword_match`),`catalog` / `init` 的 `--io-binding` 同样接受业务名。转换器 ID +去掉 `.operator.v1` 后缀(如 `text.plain`)。Catalog 的 `io_bindings` 与 `validate-io` 不再输出 +`binding_id`。全部 Pipeline 配置与 Demo 夹具已同步更新。 + 每个业务只注册一个 IoBinding:同一 `biz_name` 的第二个 binding 在注册时被拒绝并记为注册冲突 (SDK 初始化返回 -6),删除同业务多 binding 的外部契约比对与部署诊断 `BIZ_IO_CONTRACT_MISMATCH`。 发布后外部契约不兼容的变化原地修改并随新 SDK 版本发布,新旧契约须并存时新增业务,不再新增带版本的 binding。 @@ -161,10 +166,8 @@ Studio 的 `$ingress` 不再列出 `raw_request_ids`;Operator 输出的 `reque 启动,参数不变。C++ 命令行工具源码由 `src/tools/` 改名为 `src/cli/`,可执行文件仍输出到 `build/`。架构图源文件改名为 `doc/architecture_classes.puml` 与 `doc/architecture_flow.puml`。 -业务标识统一:每个业务的配置与 Demo 文件名、IoBinding ID(`<词根>.operator.v1`)和 -`biz_name`(`<词根>`,不带版本号)使用同一词根,中文名统一使用 BizDefinition 的显示名。 +业务标识统一:每个业务的配置与 Demo 文件名和 `biz_name`(`<词根>`,不带版本号)使用同一词根, +中文名统一使用 BizDefinition 的显示名。 `biz_name` 变更为 `keyword_match`、`entity_extract`、`translate`、`doc_qa`、`cross_rerank`、 -`dialogue_audit`、`ocr_invoice_qa`、`audio_asr_intent`;IoBinding `compliance_audit.operator.v1` -改为 `dialogue_audit.operator.v1`,`ocr_doc_qa.operator.v1` 改为 `ocr_invoice_qa.operator.v1`; -OCR 与语音方案的配置、Demo、数据集和 Profile 改用 `ocr_invoice_qa`、`audio_asr_intent` 词根。 +`dialogue_audit`、`ocr_invoice_qa`、`audio_asr_intent`;OCR 与语音方案的配置、Demo、数据集和 Profile 改用 `ocr_invoice_qa`、`audio_asr_intent` 词根。 Demo 结果目录随 `biz_name` 变化。正式上线前不保留旧名别名。 diff --git a/doc/architecture.md b/doc/architecture.md index f1f3614c..536c401e 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -168,8 +168,8 @@ Demo 不得提前拆解请求或在 SDK 返回后补组业务响应;内部节 - 目标共享库输出名称为 `company_alg_sdk`,产品 VERSION 为 11.0.0, SOVERSION/ABI major 为 9。 - `OperatorFunc::Create` 和配置预检都以必填部署根 `model_path` 加相对 `cfg_file_name` 解析; - `.conf` 只用 `pipe_path` 指向 Pipeline JSON;配置必须选择 `deployment.io.io_binding`, - 接入适配层据此派生内部业务边界,外部文档不保存根级 `biz_name`。模型路径只在 + `.conf` 只用 `pipe_path` 指向 Pipeline JSON;配置必须在 `deployment.io.io_binding` 填写业务名, + 接入适配层据此找到该业务的绑定与边界,外部文档不另设根级 `biz_name`。模型路径只在 `models[].model_path` 中配置,相对路径以宿主传入的部署根为基准; Pipeline 的 `deployment.io.out_mem` 按逻辑槽位归一化输出类型、分配方案、参数与容量; 最外层的独立配置读取组件按固定枚举提取配置并返回字符串,注册方案在 Create diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 46741e55..c4581c2c 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -66,7 +66,7 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 先确认实际注册,再运行一次完整路径: ```bash -./build/alg_pipeline_tool catalog --io-binding keyword_match.operator.v1 +./build/alg_pipeline_tool catalog --io-binding keyword_match ./build/alg_pipeline_tool describe-node TextRuleMatchNode ./build/alg_pipeline_tool validate configs/pipeline_keyword_match_rules.json ./build/alg_pipeline_tool plan configs/pipeline_keyword_match_rules.json @@ -91,8 +91,8 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 | Demo 数据转换 | [keyword_match_demo.cpp](../../demo/biz/keyword_match_demo.cpp) | 为新绑定补充 `REGISTER_DEMO_BIZ`;已有运行代码无法表达载体或数据集格式时,再实现输入构造与输出复制 | | 构建与部署 | [Pipeline](../../configs/pipeline_keyword_match_rules.json)、[部署配置](../../configs/pipeline_keyword_match_rules.conf) | 新增 `.cpp` 自动编入;编排业务端口,配置路径和输出容量 | -配置作者只选择 `io_binding`(`keyword_match.operator.v1`)。它关联注册的内部业务边界 -和输入/输出转换器;Demo 从配置自动选择运行入口。Operator 槽位后缀 +配置作者只在 `io_binding` 填写业务名(`keyword_match`)。它选择该业务唯一的绑定, +从而关联业务边界和输入/输出转换器;Demo 从配置自动选择运行入口。Operator 槽位后缀 (`keyword_in` / `keyword_out`)属于宿主调用契约,由绑定关联到已注册宿主类型。 ## 3. 实现并注册转换器与绑定 @@ -127,8 +127,8 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 新 `biz_name` 先定义 `BizDefinition`,声明业务名称及完整 ingress/egress typed 端口, 业务词根与各标识符的命名见[源码布局与命名](source_layout.md#标识符与定义); 调用 `PipelineCatalog::RegisterBizDefinition` 登记;业务端口契约不由转换器读写集合推导。 - 在 `IoBindingDefinition` 中指定 `binding_id`、`biz_name`、`input_converter_id`、 - `output_converter_id`,使用 `REGISTER_IO_BINDING` 注册。 + 在 `IoBindingDefinition` 中指定 `biz_name`、`input_converter_id`、`output_converter_id`, + 使用 `REGISTER_IO_BINDING` 注册;每个业务只注册一个绑定,Pipeline 以业务名选择它。 转换器的逻辑端口名就是 Blackboard Key,绑定不做改名;命名遵循本节后文的端口命名约定。 Binding 的批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖 `max_batch_size`; 转换器只在自身确有限制时才声明上限,0 表示不设限。 @@ -185,14 +185,14 @@ Definition 使用 `RequiredInputPort(port)` / `OutputPort(port)`,回调直接 | 业务(输出转换器) | `status_code` | 单条结果不满足契约时 | | --- | --- | --- | -| `keyword_match`(`keyword.result.operator.v1`) | 复制规则结果的 `status_code`;`TextRuleMatchNode` 写 0 | 未命中是正常结果(`is_hit=0`),没有无效项 | -| `audio_asr_intent`(`audio_result.plain.operator.v1`) | 复制意图结果的 `status_code` | 缺少转写或意图结果时整批失败 | -| `doc_qa`(`doc_answer.plain.operator.v1`) | 复制意图结果的 `status_code` | 缺少回答、意图或片段数时整批失败 | -| `entity_extract`(`document.structured.operator.v1`) | 0 | 解析失败或使用 fallback 的文档使整批失败 | -| `ocr_invoice_qa`(`invoice_result.plain.operator.v1`) | 0 | 同上 | -| `dialogue_audit`(`audit_result.plain.operator.v1`) | 0 | 判定解析失败或使用 fallback、首要策略排名不为 1、`risk_level` 不在 `SAFE`/`LOW_RISK`/`MEDIUM_RISK`/`HIGH_RISK`,或 `risk_score` 不在 [0, 1] 时整批失败 | -| `cross_rerank`(`rerank_result.plain.operator.v1`) | 0 | 排名不连续、超过 8 项或候选编号越界时整批失败;无候选时 `count=0` | -| `translate`(`translate.json.operator.v1`) | 0 | 译文无法序列化为 JSON(如非法 UTF-8)时整批失败 | +| `keyword_match`(`keyword.result`) | 复制规则结果的 `status_code`;`TextRuleMatchNode` 写 0 | 未命中是正常结果(`is_hit=0`),没有无效项 | +| `audio_asr_intent`(`audio_result.plain`) | 复制意图结果的 `status_code` | 缺少转写或意图结果时整批失败 | +| `doc_qa`(`doc_answer.plain`) | 复制意图结果的 `status_code` | 缺少回答、意图或片段数时整批失败 | +| `entity_extract`(`document.structured`) | 0 | 解析失败或使用 fallback 的文档使整批失败 | +| `ocr_invoice_qa`(`invoice_result.plain`) | 0 | 同上 | +| `dialogue_audit`(`audit_result.plain`) | 0 | 判定解析失败或使用 fallback、首要策略排名不为 1、`risk_level` 不在 `SAFE`/`LOW_RISK`/`MEDIUM_RISK`/`HIGH_RISK`,或 `risk_score` 不在 [0, 1] 时整批失败 | +| `cross_rerank`(`rerank_result.plain`) | 0 | 排名不连续、超过 8 项或候选编号越界时整批失败;无候选时 `count=0` | +| `translate`(`translate.json`) | 0 | 译文无法序列化为 JSON(如非法 UTF-8)时整批失败 | 需要让某个业务在单条无效时仍返回其他请求的结果,应先设计该业务的外部表达 (例如非零 `status_code` 与诊断字段)和测试,再修改对应转换器;不要在框架中跳过失败项。 diff --git a/doc/dev_guide/first_control.md b/doc/dev_guide/first_control.md index f32bee6e..58005934 100644 --- a/doc/dev_guide/first_control.md +++ b/doc/dev_guide/first_control.md @@ -75,7 +75,7 @@ mkdir -p build/control_tutorial ```json { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { diff --git a/doc/dev_guide/first_custom_node.md b/doc/dev_guide/first_custom_node.md index 2249c661..b9849ea5 100644 --- a/doc/dev_guide/first_custom_node.md +++ b/doc/dev_guide/first_custom_node.md @@ -39,7 +39,7 @@ 先查询当前构建,确认要复用的业务和节点: ```bash -./build/alg_pipeline_tool catalog --io-binding entity_extract.operator.v1 +./build/alg_pipeline_tool catalog --io-binding entity_extract ./build/alg_pipeline_tool describe-node StructuredJsonParseNode ``` diff --git a/doc/dev_guide/operator_output_allocation.md b/doc/dev_guide/operator_output_allocation.md index a9199d58..9b966fa6 100644 --- a/doc/dev_guide/operator_output_allocation.md +++ b/doc/dev_guide/operator_output_allocation.md @@ -210,7 +210,7 @@ binding.normalize_parameters = **调用前提。** `Process` 的输入、输出批次必须非空且帧数相等,单次批次不超过有效上限, 超出时直接失败,不会在门面中自动拆批。每帧按接入绑定提供必需的输入槽和输出槽,可选槽按契约省略。 提供的输出 key 预先存在且值为 null `shared_ptr`,不能传入上一批尚未释放的输出指针。 -有效 key 后缀与宿主类型可通过 `catalog --io-binding ` 查询,后缀与类型的区别见 +有效 key 后缀与宿主类型可通过 `catalog --io-binding ` 查询,后缀与类型的区别见 [选择参数](#选择参数)。 **借用输入与输出租约。** `CompanyString` 用于文本,二进制使用 `CompanyBuffer`。 diff --git a/doc/dev_guide/source_layout.md b/doc/dev_guide/source_layout.md index 8a91700b..c0886b2c 100644 --- a/doc/dev_guide/source_layout.md +++ b/doc/dev_guide/source_layout.md @@ -143,27 +143,27 @@ src/engine/ | 名称 | 含义 | | --- | --- | | `InputConverterDefinition.converter_id` / `OutputConverterDefinition.converter_id` | 独立输入、输出转换器标识 | -| `IoBindingDefinition.binding_id` | 连接业务、转换器及外部逻辑槽位的接入绑定 | -| `BizDefinition.biz_name` | 框架内部业务边界 ID,由绑定关联,例如 `doc_qa` | +| `BizDefinition.biz_name` | 业务 ID,例如 `doc_qa`;每个业务只有一个 `IoBindingDefinition`,同样以它标识 | | `NodePortDefinition.logical_name` | Node 的逻辑端口名称,由 Pipeline 映射到具体黑板键 | | `BizPortDefinition.blackboard_key` | 业务 ingress/egress 使用的实际黑板键 | -普通配置只填写 `deployment.io.io_binding`;业务边界由框架沿该绑定的注册关系获得。 +普通配置只在 `deployment.io.io_binding` 填写业务名;框架沿该业务的绑定获得业务边界。 框架沿注册关系选择转换器和槽位,不按名字拼写推导载体类型。 一个业务只使用一个 `snake_case` 词根,按 I/O 契约的实际语义命名,例如 `ocr_invoice_qa`: | 位置 | 形式 | 示例 | | --- | --- | --- | -| `biz_name`、`REGISTER_DEMO_BIZ` | `<词根>`,不带版本号 | `dialogue_audit` | -| `binding_id` | `<词根>.operator.v1`,每个业务一个 | `dialogue_audit.operator.v1` | +| `biz_name`、`deployment.io.io_binding`、`REGISTER_DEMO_BIZ` | `<词根>` | `dialogue_audit` | | 绑定源码与 Demo 源码 | `src/adapter/biz/<词根>_bindings.cpp`、`demo/biz/<词根>_demo.cpp` | `dialogue_audit_bindings.cpp` | | 方案、数据集与 Profile | `pipeline_<词根>_<变体>`、`corpus_<词根>`、`<词根>_<变体>` | `pipeline_dialogue_audit_kite.json` | | 中文名 | `BizDefinition.display_name`,Demo 标题使用同一名称 | 对话合规审核 | -转换器按数据形态命名,可被多个业务复用,不使用业务词根。 +转换器按数据形态命名,例如 `text.plain`,可被多个业务复用,不使用业务词根。 +业务名与转换器 ID 都不带版本号:发布前直接改名,发布后的不兼容变化见 +[CONTRIBUTING](../../CONTRIBUTING.md#3-design-and-current-contracts)。 -外部槽名在所属转换器 `.cpp` 内声明一次,回调与 Definition 复用;仅用一次的 schema ID、binding ID 保持原位。 +外部槽名在所属转换器 `.cpp` 内声明一次,回调与 Definition 复用;仅用一次的 schema ID 保持原位。 业务端口使用 `RequiredBizInput`、`OptionalBizInput`、`BizOutput`;Node 端口使用 `RequiredInputPort`、`OptionalInputPort`、`OutputPort`。两种端口类型不可相互隐式转换。 diff --git a/doc/developer_guide.md b/doc/developer_guide.md index 83c6bcd0..b0b6f669 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -120,7 +120,7 @@ Pipeline 仅通过 `max_parallel_workers` 控制并发上限,范围为 1–64 ## 3. 能力节点层:如何新增通用或自定义 Node -先运行 `alg_pipeline_tool catalog --io-binding ` 和 `describe-node`。只有现有操作无法闭合 +先运行 `alg_pipeline_tool catalog --io-binding ` 和 `describe-node`。只有现有操作无法闭合 typed port 契约时才新增 Node。Node 必须: - 通用操作放在 `src/common_nodes/`;领域算法与特定前后处理放在 `src/custom_nodes/`, diff --git a/doc/solutions/translate.md b/doc/solutions/translate.md index e4ebf3a8..898d9350 100644 --- a/doc/solutions/translate.md +++ b/doc/solutions/translate.md @@ -8,7 +8,7 @@ ## 直接调用 Operator API -翻译部署配置使用 `configs/pipeline_translate_cpu.conf`,其指向的 `pipeline_translate_cpu.json` 在 `deployment.io` 中配置 `io_binding` 为 `translate.operator.v1`,框架据此解析业务边界与输入/输出转换器。 +翻译部署配置使用 `configs/pipeline_translate_cpu.conf`,其指向的 `pipeline_translate_cpu.json` 在 `deployment.io` 中配置 `io_binding` 为 `translate`,框架据此解析业务边界与输入/输出转换器。 输入/输出复用已有文本载体,因此结构字段仍叫 `sentence_text` / `entities_json`。 传入的是完整对象文本,不是 `query` 子串,也不是双重 JSON 编码后的字符串: @@ -123,7 +123,7 @@ Operator 的 JSON 输出池在本配置中为 8191 字节。当前上下文为 2 ## 验证与后续需求 ```bash -./build/alg_pipeline_tool catalog --io-binding translate.operator.v1 +./build/alg_pipeline_tool catalog --io-binding translate ./build/alg_pipeline_tool validate configs/pipeline_translate_cpu.json ./build/alg_pipeline_tool plan configs/pipeline_translate_cpu.json ./build/alg_pipeline_tool resolve-conf configs/pipeline_translate_cpu.conf --root . --depth 1 diff --git a/include/adapter/io_binding.h b/include/adapter/io_binding.h index 6dea36d8..b36459dd 100644 --- a/include/adapter/io_binding.h +++ b/include/adapter/io_binding.h @@ -10,9 +10,10 @@ inline constexpr size_t kDefaultIoBindingMaxBatchSize = 64; /** * @brief 接入绑定定义 (将外部输入/输出转换器与内部 Pipeline 业务契约显式关联) + * + * 每个业务只有一个 binding,以 biz_name 标识。 */ struct IoBindingDefinition { - std::string binding_id; std::string biz_name; std::string input_converter_id; diff --git a/include/adapter/io_binding_registry.h b/include/adapter/io_binding_registry.h index eb49d38a..65088df9 100644 --- a/include/adapter/io_binding_registry.h +++ b/include/adapter/io_binding_registry.h @@ -27,7 +27,8 @@ class IoBindingRegistry { bool RegisterBinding(const IoBindingDefinition& def); - const IoBindingDefinition* FindBinding(const std::string& binding_id) const; + // Pipeline 的 deployment.io.io_binding 填写业务名,按其查找唯一的 binding。 + const IoBindingDefinition* FindBinding(const std::string& biz_name) const; std::vector AllBindings() const; @@ -47,7 +48,8 @@ class IoBindingRegistry { ~IoBindingRegistry() = default; mutable std::mutex mutex_; - std::unordered_map bindings_; + std::unordered_map + bindings_; // 按 biz_name RegistryConflicts conflicts_; }; diff --git a/plans/FRAMEWORK_SIMPLIFICATION_PLAN.md b/plans/FRAMEWORK_SIMPLIFICATION_PLAN.md index c4022f2a..d1389489 100644 --- a/plans/FRAMEWORK_SIMPLIFICATION_PLAN.md +++ b/plans/FRAMEWORK_SIMPLIFICATION_PLAN.md @@ -250,7 +250,7 @@ "inputs": { "mode": "state.mode", "normal": "normal_matches", "strict": "strict_matches" }, "outputs": { "matches": "rule_matches" } } ], - "deployment": { "io": { "io_binding": "keyword_match.operator.v1" } } + "deployment": { "io": { "io_binding": "keyword_match" } } } ``` diff --git a/src/adapter/biz/README.md b/src/adapter/biz/README.md index e498fa2f..9df38313 100644 --- a/src/adapter/biz/README.md +++ b/src/adapter/biz/README.md @@ -5,6 +5,6 @@ ## 规范与契约 - 每个业务绑定文件通过 `BizDefinition` 声明业务名、Demo 名及完整 ingress/egress Blackboard 契约,并调用 `PipelineCatalog::RegisterBizDefinition` 登记。 - 显式声明该业务支持的 Operator 绑定,选择独立的输入/输出转换器,绑定批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖 `max_batch_size`;转换器只在自身确有限制时才声明上限。 -- 业务配置通过 Pipeline 的 `deployment.io.io_binding` 显式选择全局唯一的 `binding_id`。 +- 每个业务只注册一个绑定;业务配置在 Pipeline 的 `deployment.io.io_binding` 中填写业务名来选择它。 - 转换器的逻辑端口名即 Blackboard Key,绑定不做改名;复用转换器时沿用它声明的端口名,命名约定见[业务接入指南](../../../doc/dev_guide/business_onboarding.md#3-实现并注册转换器与绑定)。 - 保留独立转换器及完整业务 ingress/egress;不能仅根据 converter 读写集合推导全部业务契约。 diff --git a/src/adapter/biz/audio_asr_intent_bindings.cpp b/src/adapter/biz/audio_asr_intent_bindings.cpp index 05a4a949..889dd3bf 100644 --- a/src/adapter/biz/audio_asr_intent_bindings.cpp +++ b/src/adapter/biz/audio_asr_intent_bindings.cpp @@ -27,11 +27,9 @@ const bool g_reg_audio_asr_intent_biz = []() { IoBindingDefinition MakeAudioAsrIntentOperatorBinding() { IoBindingDefinition def; - def.binding_id = "audio_asr_intent.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "audio.pcm.operator.v1"; - def.output_converter_id = "audio_result.plain.operator.v1"; + def.input_converter_id = "audio.pcm"; + def.output_converter_id = "audio_result.plain"; return def; } diff --git a/src/adapter/biz/cross_rerank_bindings.cpp b/src/adapter/biz/cross_rerank_bindings.cpp index 58ee7762..2077741d 100644 --- a/src/adapter/biz/cross_rerank_bindings.cpp +++ b/src/adapter/biz/cross_rerank_bindings.cpp @@ -32,11 +32,9 @@ const bool g_reg_cross_rerank_biz = []() { IoBindingDefinition MakeCrossRerankOperatorBinding() { IoBindingDefinition def; - def.binding_id = "cross_rerank.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "rerank.plain.operator.v1"; - def.output_converter_id = "rerank_result.plain.operator.v1"; + def.input_converter_id = "rerank.plain"; + def.output_converter_id = "rerank_result.plain"; return def; } diff --git a/src/adapter/biz/dialogue_audit_bindings.cpp b/src/adapter/biz/dialogue_audit_bindings.cpp index f2de8785..976ed8bc 100644 --- a/src/adapter/biz/dialogue_audit_bindings.cpp +++ b/src/adapter/biz/dialogue_audit_bindings.cpp @@ -29,11 +29,9 @@ const bool g_reg_dialogue_audit_biz = []() { IoBindingDefinition MakeDialogueAuditOperatorBinding() { IoBindingDefinition def; - def.binding_id = "dialogue_audit.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "audit.plain.operator.v1"; - def.output_converter_id = "audit_result.plain.operator.v1"; + def.input_converter_id = "audit.plain"; + def.output_converter_id = "audit_result.plain"; return def; } diff --git a/src/adapter/biz/doc_qa_bindings.cpp b/src/adapter/biz/doc_qa_bindings.cpp index 53f2aeec..f4de6272 100644 --- a/src/adapter/biz/doc_qa_bindings.cpp +++ b/src/adapter/biz/doc_qa_bindings.cpp @@ -28,11 +28,9 @@ const bool g_reg_doc_qa_biz = []() { IoBindingDefinition MakeDocQaOperatorBinding() { IoBindingDefinition def; - def.binding_id = "doc_qa.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "doc_query.plain.operator.v1"; - def.output_converter_id = "doc_answer.plain.operator.v1"; + def.input_converter_id = "doc_query.plain"; + def.output_converter_id = "doc_answer.plain"; return def; } diff --git a/src/adapter/biz/entity_extract_bindings.cpp b/src/adapter/biz/entity_extract_bindings.cpp index 545bdf03..f48e7050 100644 --- a/src/adapter/biz/entity_extract_bindings.cpp +++ b/src/adapter/biz/entity_extract_bindings.cpp @@ -27,11 +27,9 @@ const bool g_reg_entity_extract_biz = []() { IoBindingDefinition MakeEntityExtractOperatorBinding() { IoBindingDefinition def; - def.binding_id = "entity_extract.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "text.plain.operator.v1"; - def.output_converter_id = "document.structured.operator.v1"; + def.input_converter_id = "text.plain"; + def.output_converter_id = "document.structured"; return def; } diff --git a/src/adapter/biz/keyword_match_bindings.cpp b/src/adapter/biz/keyword_match_bindings.cpp index 6df2a5ab..2bb64eb6 100644 --- a/src/adapter/biz/keyword_match_bindings.cpp +++ b/src/adapter/biz/keyword_match_bindings.cpp @@ -27,11 +27,9 @@ const bool g_reg_keyword_match_biz = []() { IoBindingDefinition MakeKeywordMatchOperatorBinding() { IoBindingDefinition def; - def.binding_id = "keyword_match.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "keyword.plain.operator.v1"; - def.output_converter_id = "keyword.result.operator.v1"; + def.input_converter_id = "keyword.plain"; + def.output_converter_id = "keyword.result"; return def; } diff --git a/src/adapter/biz/ocr_invoice_qa_bindings.cpp b/src/adapter/biz/ocr_invoice_qa_bindings.cpp index 6300988f..28b8e374 100644 --- a/src/adapter/biz/ocr_invoice_qa_bindings.cpp +++ b/src/adapter/biz/ocr_invoice_qa_bindings.cpp @@ -27,11 +27,9 @@ const bool g_reg_ocr_invoice_qa_biz = []() { IoBindingDefinition MakeOcrInvoiceQaOperatorBinding() { IoBindingDefinition def; - def.binding_id = "ocr_invoice_qa.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "image_query.plain.operator.v1"; - def.output_converter_id = "invoice_result.plain.operator.v1"; + def.input_converter_id = "image_query.plain"; + def.output_converter_id = "invoice_result.plain"; return def; } diff --git a/src/adapter/biz/translate_bindings.cpp b/src/adapter/biz/translate_bindings.cpp index 7740aa43..437b6550 100644 --- a/src/adapter/biz/translate_bindings.cpp +++ b/src/adapter/biz/translate_bindings.cpp @@ -27,11 +27,9 @@ const bool g_reg_translate_biz = []() { IoBindingDefinition MakeTranslateOperatorBinding() { IoBindingDefinition def; - def.binding_id = "translate.operator.v1"; def.biz_name = kBizName; - - def.input_converter_id = "translate.json.operator.v1"; - def.output_converter_id = "translate.json.operator.v1"; + def.input_converter_id = "translate.json"; + def.output_converter_id = "translate.json"; return def; } diff --git a/src/adapter/deployment_preparation.cpp b/src/adapter/deployment_preparation.cpp index 1d14ba1a..9b2423d8 100644 --- a/src/adapter/deployment_preparation.cpp +++ b/src/adapter/deployment_preparation.cpp @@ -70,14 +70,14 @@ bool PrepareDeploymentDocument(const nlohmann::json& document, } // S2: 从明确的 I/O 绑定取得内部业务身份。 - const auto& binding_id = doc_split.deployment.io.io_binding; - const auto* binding = IoBindingRegistry::Instance().FindBinding(binding_id); + const auto& io_binding = doc_split.deployment.io.io_binding; + const auto* binding = IoBindingRegistry::Instance().FindBinding(io_binding); if (!binding) { if (diagnostic) { diagnostic->code = "UNKNOWN_IO_BINDING"; diagnostic->path = "/deployment/io/io_binding"; diagnostic->message = - "Unknown or unregistered io_binding: " + binding_id + + "Unknown or unregistered io_binding: " + io_binding + " (at /deployment/io/io_binding)"; } return false; @@ -131,7 +131,7 @@ bool PrepareDeploymentDocument(const nlohmann::json& document, diagnostic->code = "DEPLOYMENT_ERROR"; diagnostic->path = "/deployment/io/io_binding"; diagnostic->message = - "Binding '" + binding->binding_id + + "Binding '" + binding->biz_name + "' declares no batch limit on the binding or its converters"; } return false; diff --git a/src/adapter/input/audio_input.cpp b/src/adapter/input/audio_input.cpp index 34b8cc39..0d61db4c 100644 --- a/src/adapter/input/audio_input.cpp +++ b/src/adapter/input/audio_input.cpp @@ -51,7 +51,7 @@ int DecodeOperatorAudioInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorAudioInputConverter() { InputConverterDefinition def; - def.converter_id = "audio.pcm.operator.v1"; + def.converter_id = "audio.pcm"; def.schema_id = "audio.pcm.request"; diff --git a/src/adapter/input/audit_input.cpp b/src/adapter/input/audit_input.cpp index 1537d5d5..d8849e78 100644 --- a/src/adapter/input/audit_input.cpp +++ b/src/adapter/input/audit_input.cpp @@ -87,7 +87,7 @@ int DecodeOperatorAuditInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorAuditInputConverter() { InputConverterDefinition def; - def.converter_id = "audit.plain.operator.v1"; + def.converter_id = "audit.plain"; def.schema_id = "audit.plain.request"; diff --git a/src/adapter/input/doc_query_input.cpp b/src/adapter/input/doc_query_input.cpp index 668dd34a..842498c0 100644 --- a/src/adapter/input/doc_query_input.cpp +++ b/src/adapter/input/doc_query_input.cpp @@ -86,7 +86,7 @@ int DecodeOperatorDocQueryInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorDocQueryInputConverter() { InputConverterDefinition def; - def.converter_id = "doc_query.plain.operator.v1"; + def.converter_id = "doc_query.plain"; def.schema_id = "doc_query.plain.request"; diff --git a/src/adapter/input/image_query_input.cpp b/src/adapter/input/image_query_input.cpp index bab7fed6..9e85a525 100644 --- a/src/adapter/input/image_query_input.cpp +++ b/src/adapter/input/image_query_input.cpp @@ -87,7 +87,7 @@ int DecodeOperatorImageQueryInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorImageQueryInputConverter() { InputConverterDefinition def; - def.converter_id = "image_query.plain.operator.v1"; + def.converter_id = "image_query.plain"; def.schema_id = "image_query.plain.request"; diff --git a/src/adapter/input/rerank_input.cpp b/src/adapter/input/rerank_input.cpp index b6a403bc..99254dd4 100644 --- a/src/adapter/input/rerank_input.cpp +++ b/src/adapter/input/rerank_input.cpp @@ -109,7 +109,7 @@ int DecodeOperatorRerankInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorRerankInputConverter() { InputConverterDefinition def; - def.converter_id = "rerank.plain.operator.v1"; + def.converter_id = "rerank.plain"; def.schema_id = "rerank.plain.request"; diff --git a/src/adapter/input/text_input.cpp b/src/adapter/input/text_input.cpp index 76534883..fddf2520 100644 --- a/src/adapter/input/text_input.cpp +++ b/src/adapter/input/text_input.cpp @@ -50,7 +50,7 @@ int DecodeOperatorKeywordInput(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorEntityInputConverter() { InputConverterDefinition def; - def.converter_id = "text.plain.operator.v1"; + def.converter_id = "text.plain"; def.schema_id = "text.plain.request"; @@ -63,7 +63,7 @@ InputConverterDefinition MakeOperatorEntityInputConverter() { InputConverterDefinition MakeOperatorKeywordInputConverter() { InputConverterDefinition def; - def.converter_id = "keyword.plain.operator.v1"; + def.converter_id = "keyword.plain"; def.schema_id = "text.plain.request"; diff --git a/src/adapter/input/translate_json_input.cpp b/src/adapter/input/translate_json_input.cpp index 58ad0874..2dd819ce 100644 --- a/src/adapter/input/translate_json_input.cpp +++ b/src/adapter/input/translate_json_input.cpp @@ -57,7 +57,7 @@ int DecodeOperatorTranslateJson(const ExternalInputBatchView& source, InputConverterDefinition MakeOperatorTranslateJsonInputConverter() { InputConverterDefinition def; - def.converter_id = "translate.json.operator.v1"; + def.converter_id = "translate.json"; def.schema_id = "translate.json.request"; diff --git a/src/adapter/io_binding_registry.cpp b/src/adapter/io_binding_registry.cpp index f043d81f..cedbf9e3 100644 --- a/src/adapter/io_binding_registry.cpp +++ b/src/adapter/io_binding_registry.cpp @@ -40,49 +40,32 @@ IoBindingRegistry& IoBindingRegistry::Instance() { bool IoBindingRegistry::RegisterBinding(const IoBindingDefinition& def) { std::lock_guard lock(mutex_); - if (def.binding_id.empty()) { - conflicts_.Record("Empty binding_id in IoBindingDefinition"); - return false; - } if (def.biz_name.empty()) { - conflicts_.Record("Empty biz_name in IoBindingDefinition: " + - def.binding_id); + conflicts_.Record("Empty biz_name in IoBindingDefinition"); return false; } - if (def.input_converter_id.empty()) { conflicts_.Record("Empty input_converter_id in IoBindingDefinition: " + - def.binding_id); + def.biz_name); return false; } if (def.output_converter_id.empty()) { conflicts_.Record("Empty output_converter_id in IoBindingDefinition: " + - def.binding_id); - return false; - } - - auto it = bindings_.find(def.binding_id); - if (it != bindings_.end()) { - conflicts_.Record("Duplicate IoBinding registration: " + def.binding_id); + def.biz_name); return false; } // 一个业务只有一份外部契约,因此只登记一个 binding。 - for (const auto& [id, existing] : bindings_) { - if (existing.biz_name == def.biz_name) { - conflicts_.Record("Duplicate IoBinding for biz_name '" + def.biz_name + - "': " + id + ", " + def.binding_id); - return false; - } + if (!bindings_.emplace(def.biz_name, def).second) { + conflicts_.Record("Duplicate IoBinding for biz_name: " + def.biz_name); + return false; } - - bindings_[def.binding_id] = def; return true; } const IoBindingDefinition* IoBindingRegistry::FindBinding( - const std::string& binding_id) const { + const std::string& biz_name) const { std::lock_guard lock(mutex_); - auto it = bindings_.find(binding_id); + auto it = bindings_.find(biz_name); if (it != bindings_.end()) { return &it->second; } @@ -121,20 +104,19 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { const auto catalog_snapshot = PipelineCatalog::Snapshot(); - for (const auto& [binding_id, binding] : bindings_) { + for (const auto& [biz_name, binding] : bindings_) { // 1. 检查 biz_name 是否在 PipelineCatalog 中已注册 - const auto* biz_def = catalog_snapshot.FindBiz(binding.biz_name); + const auto* biz_def = catalog_snapshot.FindBiz(biz_name); if (!biz_def) { - errors.push_back( - "Binding '" + binding_id + - "' references unregistered biz_name: " + binding.biz_name); + errors.push_back("Binding '" + biz_name + + "' references unregistered biz_name"); } // 2. 检查 input converter:逻辑端口名即业务入口键 const auto* in_conv = conv_reg.FindInputConverter(binding.input_converter_id); if (!in_conv) { - errors.push_back("Binding '" + binding_id + + errors.push_back("Binding '" + biz_name + "' references unregistered input_converter: " + binding.input_converter_id); } else if (biz_def) { @@ -146,10 +128,10 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { return p.blackboard_key == target_key; }); if (ingress_it == biz_def->ingress.end()) { - errors.push_back("Binding '" + binding_id + "' input port '" + + errors.push_back("Binding '" + biz_name + "' input port '" + port.logical_name + "' is not a biz ingress key"); } else if (port.type_id != ingress_it->type_id) { - errors.push_back("Binding '" + binding_id + "' input port '" + + errors.push_back("Binding '" + biz_name + "' input port '" + port.logical_name + "' type '" + port.type_id + "' does not match biz ingress key '" + target_key + "' type '" + ingress_it->type_id + "'"); @@ -164,7 +146,7 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { return port.logical_name == ingress_port.blackboard_key; }); if (!covered) { - errors.push_back("Binding '" + binding_id + + errors.push_back("Binding '" + biz_name + "' missing required biz ingress port: " + ingress_port.blackboard_key); } @@ -175,7 +157,7 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { const auto* out_conv = conv_reg.FindOutputConverter(binding.output_converter_id); if (!out_conv) { - errors.push_back("Binding '" + binding_id + + errors.push_back("Binding '" + biz_name + "' references unregistered output_converter: " + binding.output_converter_id); } else if (biz_def) { @@ -190,20 +172,20 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { biz_def->ingress.end(), matches_key); if (egress_it != biz_def->egress.end()) { if (port.type_id != egress_it->type_id) { - errors.push_back("Binding '" + binding_id + "' output port '" + + errors.push_back("Binding '" + biz_name + "' output port '" + port.logical_name + "' type '" + port.type_id + "' does not match biz egress key '" + target_key + "' type '" + egress_it->type_id + "'"); } } else if (ingress_it != biz_def->ingress.end()) { if (port.type_id != ingress_it->type_id) { - errors.push_back("Binding '" + binding_id + "' output port '" + + errors.push_back("Binding '" + biz_name + "' output port '" + port.logical_name + "' type '" + port.type_id + "' does not match biz ingress key '" + target_key + "' type '" + ingress_it->type_id + "'"); } } else { - errors.push_back("Binding '" + binding_id + "' output port '" + + errors.push_back("Binding '" + biz_name + "' output port '" + port.logical_name + "' is not a biz egress or ingress key"); } @@ -217,7 +199,7 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { return port.logical_name == egress_port.blackboard_key; }); if (!covered) { - errors.push_back("Binding '" + binding_id + + errors.push_back("Binding '" + biz_name + "' missing required biz egress port: " + egress_port.blackboard_key); } @@ -226,7 +208,7 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { if (in_conv && out_conv && EffectiveMaxBatchSize(binding, *in_conv, *out_conv) == 0) { - errors.push_back("Binding '" + binding_id + + errors.push_back("Binding '" + biz_name + "' declares no batch limit: set max_batch_size on the " "binding or one of its converters"); } @@ -241,10 +223,10 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { slot.type_suffix); if (!val_binding) { errors.push_back( - "Binding '" + binding_id + "' input slot '" + slot.slot_name + + "Binding '" + biz_name + "' input slot '" + slot.slot_name + "' uses unregistered ValueType suffix: " + slot.type_suffix); } else if (!val_binding->validate_external) { - errors.push_back("Binding '" + binding_id + "' input slot '" + + errors.push_back("Binding '" + biz_name + "' input slot '" + slot.slot_name + "' ValueType suffix '" + slot.type_suffix + "' missing validate_external"); } @@ -258,7 +240,7 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { slot.type_suffix, ""); if (!val_binding) { errors.push_back( - "Binding '" + binding_id + "' output slot '" + slot.slot_name + + "Binding '" + biz_name + "' output slot '" + slot.slot_name + "' uses unregistered ValueType suffix: " + slot.type_suffix); } } diff --git a/src/adapter/io_catalog.cpp b/src/adapter/io_catalog.cpp index dcac3486..5f3cf504 100644 --- a/src/adapter/io_catalog.cpp +++ b/src/adapter/io_catalog.cpp @@ -55,8 +55,7 @@ nlohmann::json OutputConverterToJson(const OutputConverterDefinition& conv) { } nlohmann::json IoBindingToJson(const IoBindingDefinition& b) { - return {{"binding_id", b.binding_id}, - {"biz_name", b.biz_name}, + return {{"biz_name", b.biz_name}, {"transport", "operator"}, {"input_converter_id", b.input_converter_id}, {"output_converter_id", b.output_converter_id}, @@ -71,7 +70,7 @@ nlohmann::json IoCatalog::ToJson(const PipelineCatalogSnapshot& snapshot, auto all_bindings = IoBindingRegistry::Instance().AllBindings(); std::sort(all_bindings.begin(), all_bindings.end(), [](const IoBindingDefinition& a, const IoBindingDefinition& b) { - return a.binding_id < b.binding_id; + return a.biz_name < b.biz_name; }); std::set active_input_converters; diff --git a/src/adapter/output/audio_result_output.cpp b/src/adapter/output/audio_result_output.cpp index db3b7bba..d9617a29 100644 --- a/src/adapter/output/audio_result_output.cpp +++ b/src/adapter/output/audio_result_output.cpp @@ -89,7 +89,7 @@ int EncodeOperatorAudioResult(AlgContext* context, OutputConverterDefinition MakeOperatorAudioResultOutputConverter() { OutputConverterDefinition def; - def.converter_id = "audio_result.plain.operator.v1"; + def.converter_id = "audio_result.plain"; def.schema_id = "audio_result.plain.response"; diff --git a/src/adapter/output/audit_result_output.cpp b/src/adapter/output/audit_result_output.cpp index 1a306dc0..6094a2a6 100644 --- a/src/adapter/output/audit_result_output.cpp +++ b/src/adapter/output/audit_result_output.cpp @@ -131,7 +131,7 @@ int EncodeOperatorAuditResult(AlgContext* context, OutputConverterDefinition MakeOperatorAuditResultOutputConverter() { OutputConverterDefinition def; - def.converter_id = "audit_result.plain.operator.v1"; + def.converter_id = "audit_result.plain"; def.schema_id = "audit_result.plain.response"; diff --git a/src/adapter/output/doc_answer_output.cpp b/src/adapter/output/doc_answer_output.cpp index 8be0c373..1c247eb4 100644 --- a/src/adapter/output/doc_answer_output.cpp +++ b/src/adapter/output/doc_answer_output.cpp @@ -93,7 +93,7 @@ int EncodeOperatorDocAnswer(AlgContext* context, OutputConverterDefinition MakeOperatorDocAnswerOutputConverter() { OutputConverterDefinition def; - def.converter_id = "doc_answer.plain.operator.v1"; + def.converter_id = "doc_answer.plain"; def.schema_id = "doc_answer.plain.response"; diff --git a/src/adapter/output/invoice_result_output.cpp b/src/adapter/output/invoice_result_output.cpp index 629c2db0..f56b2464 100644 --- a/src/adapter/output/invoice_result_output.cpp +++ b/src/adapter/output/invoice_result_output.cpp @@ -87,7 +87,7 @@ int EncodeOperatorInvoiceResult(AlgContext* context, OutputConverterDefinition MakeOperatorInvoiceResultOutputConverter() { OutputConverterDefinition def; - def.converter_id = "invoice_result.plain.operator.v1"; + def.converter_id = "invoice_result.plain"; def.schema_id = "invoice_result.plain.response"; diff --git a/src/adapter/output/keyword_result_output.cpp b/src/adapter/output/keyword_result_output.cpp index a93ab121..3590c02b 100644 --- a/src/adapter/output/keyword_result_output.cpp +++ b/src/adapter/output/keyword_result_output.cpp @@ -36,7 +36,7 @@ int EncodeOperatorKeywordResult(AlgContext* context, OutputConverterDefinition MakeOperatorKeywordResultOutputConverter() { OutputConverterDefinition def; - def.converter_id = "keyword.result.operator.v1"; + def.converter_id = "keyword.result"; def.schema_id = "keyword.result.response"; diff --git a/src/adapter/output/rerank_result_output.cpp b/src/adapter/output/rerank_result_output.cpp index 90f21f5f..6efaa93e 100644 --- a/src/adapter/output/rerank_result_output.cpp +++ b/src/adapter/output/rerank_result_output.cpp @@ -94,7 +94,7 @@ int EncodeOperatorRerankResult(AlgContext* context, OutputConverterDefinition MakeOperatorRerankResultOutputConverter() { OutputConverterDefinition def; - def.converter_id = "rerank_result.plain.operator.v1"; + def.converter_id = "rerank_result.plain"; def.schema_id = "rerank_result.plain.response"; diff --git a/src/adapter/output/structured_document_output.cpp b/src/adapter/output/structured_document_output.cpp index bd996e2f..2876bc34 100644 --- a/src/adapter/output/structured_document_output.cpp +++ b/src/adapter/output/structured_document_output.cpp @@ -39,7 +39,7 @@ int EncodeOperatorStructuredDocument(AlgContext* context, OutputConverterDefinition MakeOperatorStructuredDocumentOutputConverter() { OutputConverterDefinition def; - def.converter_id = "document.structured.operator.v1"; + def.converter_id = "document.structured"; def.schema_id = "document.structured.response"; diff --git a/src/adapter/output/translation_json_output.cpp b/src/adapter/output/translation_json_output.cpp index f1a10472..40fa8bb6 100644 --- a/src/adapter/output/translation_json_output.cpp +++ b/src/adapter/output/translation_json_output.cpp @@ -37,7 +37,7 @@ int EncodeOperatorTranslationJson(AlgContext* context, OutputConverterDefinition MakeOperatorTranslationJsonOutputConverter() { OutputConverterDefinition def; - def.converter_id = "translate.json.operator.v1"; + def.converter_id = "translate.json"; def.schema_id = "translate.json.response"; diff --git a/src/adapter/pipeline_document.cpp b/src/adapter/pipeline_document.cpp index e742f513..6552ddc7 100644 --- a/src/adapter/pipeline_document.cpp +++ b/src/adapter/pipeline_document.cpp @@ -108,7 +108,7 @@ bool SplitPipelineDocument(const nlohmann::json& root, if (out_error_path) *out_error_path = "/deployment/io/io_binding"; return false; } - std::string binding_id = io["io_binding"].get(); + std::string io_binding = io["io_binding"].get(); if (structure::TooShort(io["io_binding"], structure::Property(io_shape, "io_binding"))) { if (out_error) { @@ -117,7 +117,7 @@ bool SplitPipelineDocument(const nlohmann::json& root, if (out_error_path) *out_error_path = "/deployment/io/io_binding"; return false; } - out_split->deployment.io.io_binding = std::move(binding_id); + out_split->deployment.io.io_binding = std::move(io_binding); } if (io.contains("out_mem")) { diff --git a/src/cli/alg_pipeline_tool.cpp b/src/cli/alg_pipeline_tool.cpp index 4e2d97dd..a8468419 100644 --- a/src/cli/alg_pipeline_tool.cpp +++ b/src/cli/alg_pipeline_tool.cpp @@ -163,7 +163,7 @@ nlohmann::json ProfilesJson(const std::string& biz_filter, std::string* error) { if (!biz_filter.empty() && binding->biz_name != biz_filter) continue; result.push_back( {{"name", name}, - {"io_binding", binding->binding_id}, + {"io_binding", binding->biz_name}, {"biz_name", binding->biz_name}, {"config", profile.value("config", "")}, {"dataset", profile.value("dataset", "")}, @@ -250,7 +250,7 @@ nlohmann::json ResolveConf(const std::string& file, const std::string& root, {"resolved", model.resolved_model_path}}); nlohmann::json configuration = { {"biz_name", resolved.io_plan->binding.biz_name}, - {"io_binding", resolved.io_plan->binding.binding_id}, + {"io_binding", resolved.io_plan->binding.biz_name}, {"conf_path", resolved.conf_path.string()}, {"pipeline_path", resolved.pipeline_path.string()}, {"model_root", resolved.model_root_path.string()}, @@ -267,12 +267,12 @@ nlohmann::json ResolveConf(const std::string& file, const std::string& root, void Usage() { std::cerr << "Usage:\n" - << " alg_pipeline_tool catalog [--io-binding ID]\n" + << " alg_pipeline_tool catalog [--io-binding BIZ]\n" << " alg_pipeline_tool export-schema\n" << " alg_pipeline_tool describe-node NODE_TYPE\n" << " alg_pipeline_tool describe-model MODEL_TYPE\n" << " alg_pipeline_tool describe-backend BACKEND_TYPE\n" - << " alg_pipeline_tool init --io-binding ID [--profile " + << " alg_pipeline_tool init --io-binding BIZ [--profile " "NAME|--empty] [--raw]\n" << " alg_pipeline_tool validate FILE|--stdin [--explain]\n" << " alg_pipeline_tool plan FILE|--stdin [--explain]\n"; @@ -444,7 +444,7 @@ int main(int argc, char* argv[]) { } if (command == "init") { - std::string binding_id; + std::string io_binding; std::string profile; bool empty = false; bool raw = false; @@ -452,8 +452,8 @@ int main(int argc, char* argv[]) { std::string arg = argv[i]; const bool has_value = i + 1 < argc && argv[i + 1][0] != '\0' && argv[i + 1][0] != '-'; - if (arg == "--io-binding" && binding_id.empty() && has_value) - binding_id = argv[++i]; + if (arg == "--io-binding" && io_binding.empty() && has_value) + io_binding = argv[++i]; else if (arg == "--profile" && profile.empty() && has_value) profile = argv[++i]; else if (arg == "--empty" && !empty) @@ -465,19 +465,19 @@ int main(int argc, char* argv[]) { return 2; } } - if (binding_id.empty() || (empty && !profile.empty())) { + if (io_binding.empty() || (empty && !profile.empty())) { Usage(); return 2; } const auto* binding = - llm_edgeflow::IoBindingRegistry::Instance().FindBinding(binding_id); + llm_edgeflow::IoBindingRegistry::Instance().FindBinding(io_binding); if (!binding) { - std::cout << ToolError("UNKNOWN_IO_BINDING", binding_id).dump(2) + std::cout << ToolError("UNKNOWN_IO_BINDING", io_binding).dump(2) << std::endl; return 1; } nlohmann::json pipeline = { - {"deployment", {{"io", {{"io_binding", binding_id}}}}}, + {"deployment", {{"io", {{"io_binding", io_binding}}}}}, {"models", nlohmann::json::array()}, {"pipeline", nlohmann::json::array()}}; if (!profile.empty()) { @@ -500,7 +500,7 @@ int main(int argc, char* argv[]) { } if (!path || !ReadJson(path->string(), &pipeline, &error) || !DocumentBinding(pipeline) || - DocumentBinding(pipeline)->binding_id != binding_id) { + DocumentBinding(pipeline)->biz_name != io_binding) { std::cout << ToolError("PROFILE_MISMATCH", "Profile is unavailable or belongs to another " "I/O binding") @@ -644,7 +644,6 @@ int main(int argc, char* argv[]) { } nlohmann::json binding_info = { - {"binding_id", plan->binding.binding_id}, {"biz_name", plan->binding.biz_name}, {"transport", "operator"}, {"input_converter_id", plan->binding.input_converter_id}, diff --git a/src/cli/pipeline_json_schema.cpp b/src/cli/pipeline_json_schema.cpp index 729df25b..af3dc9aa 100644 --- a/src/cli/pipeline_json_schema.cpp +++ b/src/cli/pipeline_json_schema.cpp @@ -107,7 +107,7 @@ Json Models(const Json& catalog) { Json Deployment(const Json& catalog) { auto result = DeploymentStructure(); result["properties"]["io"]["properties"]["io_binding"] = - Choices(catalog.at("io_bindings"), "binding_id"); + Choices(catalog.at("io_bindings"), "biz_name"); return result; } } // namespace diff --git a/src/custom_nodes/README.md b/src/custom_nodes/README.md index 450d91af..e0b028e2 100644 --- a/src/custom_nodes/README.md +++ b/src/custom_nodes/README.md @@ -14,7 +14,7 @@ Map、Batch、LLM 是同一契约的便利组合;`NodeBase` 是框架内部运 ## 通用开发步骤速查 -1. 查询 `build/alg_pipeline_tool catalog --io-binding ` 和 `describe-node`,确认已有能力。 +1. 查询 `build/alg_pipeline_tool catalog --io-binding ` 和 `describe-node`,确认已有能力。 2. 生成普通函数、Spec 和对应测试: ```bash diff --git a/tests/RuntimeTests.cmake b/tests/RuntimeTests.cmake index d87e4325..b3dbca27 100644 --- a/tests/RuntimeTests.cmake +++ b/tests/RuntimeTests.cmake @@ -469,7 +469,7 @@ foreach(config_path IN LISTS EDGEFLOW_PIPELINE_CONFIGS) endforeach() add_test(NAME PipelineToolCatalogTest COMMAND $ - catalog --io-binding keyword_match.operator.v1) + catalog --io-binding keyword_match) add_test(NAME PipelineToolValidateTest COMMAND $ validate ${PROJECT_SOURCE_DIR}/configs/pipeline_keyword_match_rules.json) set_tests_properties(PipelineToolCatalogTest PipelineToolValidateTest diff --git a/tests/contract/abi/test_adapter_contract_security.cpp b/tests/contract/abi/test_adapter_contract_security.cpp index 0bd77dbc..9a8c58ad 100644 --- a/tests/contract/abi/test_adapter_contract_security.cpp +++ b/tests/contract/abi/test_adapter_contract_security.cpp @@ -165,7 +165,7 @@ TEST_F(AdapterContractSecurityTest, model_config["backend_config"] = nlohmann::json::object(); pipeline["deployment"] = { {"io", - {{"io_binding", "translate.operator.v1"}, + {{"io_binding", "translate"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -304,15 +304,15 @@ TEST_F(AdapterContractSecurityTest, TEST_F(AdapterContractSecurityTest, TranslationLiteralResultPackingAndCarrierSafety) { - const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(converter, nullptr); const std::string translation(2200, 'x'); AlgContext large; std::vector request_ids{123}; large.Publish(kLlmAnswers, TextBatch{{0, 0, translation}}); OutputEncodeOptions options; - options.converter_id = "translate.json.operator.v1"; + options.converter_id = "translate.json"; options.request_ids = &request_ids; AdapterStatus status; @@ -680,8 +680,8 @@ TEST_F(AdapterContractSecurityTest, NestedPointerTreeDepthProtection) { // 4. COPY_IN 内存所有权深度隔离测试 (ADP-002, RECHECK-006) // --------------------------------------------------------------------------- TEST_F(AdapterContractSecurityTest, DirectUnpackMemoryIsolation) { - const auto* input_conv = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.operator.v1"); + const auto* input_conv = + IoConverterRegistry::Instance().FindInputConverter("keyword.plain"); ASSERT_NE(input_conv, nullptr); // 创建动态可修改的原始缓冲区 @@ -698,7 +698,7 @@ TEST_F(AdapterContractSecurityTest, DirectUnpackMemoryIsolation) { in_view.slots["keyword_in"] = llm_edgeflow::BorrowInputForTest({&in_struct}); in_view.slot_types["keyword_in"] = "CompanyOperatorKeywordInput"; InputDecodeOptions in_options; - in_options.converter_id = "keyword.plain.operator.v1"; + in_options.converter_id = "keyword.plain"; std::vector request_ids; in_options.request_ids = &request_ids; @@ -750,25 +750,20 @@ TEST_F(AdapterContractSecurityTest, OutputStringTruncationRejection) { // --------------------------------------------------------------------------- TEST_F(AdapterContractSecurityTest, PipelineBindingFailClosedAndExactMatch) { const auto* binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match"); ASSERT_NE(binding, nullptr); // 6.1 精确匹配成功 EXPECT_EQ(binding->biz_name, "keyword_match"); - // 6.2 旧 cabi / 包含子串的伪造名称 / 大小写不匹配 / 空白名称均严格拒绝 - // (Fail-Closed) - EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("keyword_match.cabi.v1"), - nullptr); + // 6.2 包含子串的伪造名称 / 大小写不匹配 / 空白名称均严格拒绝 (Fail-Closed) EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("keyword_match_fake"), nullptr); EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("my_keyword_match"), nullptr); - EXPECT_EQ( - IoBindingRegistry::Instance().FindBinding("KEYWORD_MATCH.OPERATOR.V1"), - nullptr); - // 业务名与 binding ID 共用词根,但业务名本身不是 binding ID。 - EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("keyword_match"), + EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("KEYWORD_MATCH"), + nullptr); + EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(" keyword_match"), nullptr); EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(""), nullptr); @@ -876,8 +871,8 @@ TEST_F(AdapterContractSecurityTest, ConcurrentStatelessAdapterExecution) { // 跨样本的载体错误与 biz 解码错误的优先级 TEST_F(AdapterContractSecurityTest, TranslationCrossSampleCarrierVsBizErrorPriority) { - const auto* converter = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(converter, nullptr); // 样本 0 有载体错误 (字符串超长) @@ -893,7 +888,7 @@ TEST_F(AdapterContractSecurityTest, carrier_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; InputDecodeOptions options; - options.converter_id = "translate.json.operator.v1"; + options.converter_id = "translate.json"; AlgContext carrier_ctx; AdapterStatus carrier_status; @@ -903,7 +898,7 @@ TEST_F(AdapterContractSecurityTest, EXPECT_EQ(carrier_status.SampleIndex(), 0); EXPECT_EQ(carrier_status.FieldPath(), "sentence_text"); - // 纯 biz 解码错误保留 translate.json.operator.v1 Converter 名称 + // 纯 biz 解码错误保留 translate.json Converter 名称 std::string bad_json = "{\"wrong_field\":123}"; CompanyString cs_biz{static_cast(bad_json.size()), const_cast(bad_json.data())}; @@ -917,15 +912,15 @@ TEST_F(AdapterContractSecurityTest, AdapterStatus biz_status; EXPECT_EQ(converter->decode_fn(biz_view, options, &biz_ctx, &biz_status), COMPANY_ALG_ERR_INVALID_INPUT); - EXPECT_EQ(biz_status.AdapterName(), "translate.json.operator.v1"); + EXPECT_EQ(biz_status.AdapterName(), "translate.json"); EXPECT_EQ(biz_status.FieldPath(), "json"); } // 返回码与 AdapterStatus 相互独立 TEST_F(AdapterContractSecurityTest, TranslationReturnCodeAndAdapterStatusIndependence) { - const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(converter, nullptr); // 存在请求 ID 表,但缺少答案 @@ -939,7 +934,7 @@ TEST_F(AdapterContractSecurityTest, view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; OutputEncodeOptions options; - options.converter_id = "translate.json.operator.v1"; + options.converter_id = "translate.json"; options.request_ids = &request_ids; size_t written = 0; @@ -954,8 +949,8 @@ TEST_F(AdapterContractSecurityTest, // 翻译序列化失败 (非法 UTF-8) 优先于容量检查 TEST_F(AdapterContractSecurityTest, TranslationSerializationFailurePriorityOverCapacity) { - const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(converter, nullptr); // 存在请求 ID 表,但答案含非法 UTF-8 字节序列 @@ -971,7 +966,7 @@ TEST_F(AdapterContractSecurityTest, view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; OutputEncodeOptions options; - options.converter_id = "translate.json.operator.v1"; + options.converter_id = "translate.json"; options.request_ids = &request_ids; size_t written = 0; @@ -985,11 +980,11 @@ TEST_F(AdapterContractSecurityTest, // 翻译在 AlgContext 为空时的诊断 TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(out_conv, nullptr); // 1. context 为空时 Decode 必须返回 INVALID_INPUT (-3),字段为 "context" @@ -1003,7 +998,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { in_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; InputDecodeOptions in_options; - in_options.converter_id = "translate.json.operator.v1"; + in_options.converter_id = "translate.json"; AdapterStatus unpack_status; int unpack_ret = @@ -1011,7 +1006,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { EXPECT_EQ(unpack_ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(unpack_status.Code(), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(unpack_status.FieldPath(), "context"); - EXPECT_EQ(unpack_status.AdapterName(), "translate.json.operator.v1"); + EXPECT_EQ(unpack_status.AdapterName(), "translate.json"); EXPECT_EQ(in_conv->decode_fn(in_view, in_options, nullptr, nullptr), COMPANY_ALG_ERR_INVALID_INPUT); @@ -1024,7 +1019,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { out_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; OutputEncodeOptions out_options; - out_options.converter_id = "translate.json.operator.v1"; + out_options.converter_id = "translate.json"; size_t written = 0; AdapterStatus pack_status; @@ -1033,7 +1028,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { EXPECT_EQ(pack_ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(pack_status.Code(), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(pack_status.FieldPath(), "context"); - EXPECT_EQ(pack_status.AdapterName(), "translate.json.operator.v1"); + EXPECT_EQ(pack_status.AdapterName(), "translate.json"); EXPECT_EQ( out_conv->encode_fn(nullptr, out_options, &out_view, &written, nullptr), @@ -1042,10 +1037,10 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { TEST_F(AdapterContractSecurityTest, DecodeAndEncodeRejectMissingRequestIdTable) { - const auto* input = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.operator.v1"); - const auto* output = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.operator.v1"); + const auto* input = + IoConverterRegistry::Instance().FindInputConverter("keyword.plain"); + const auto* output = + IoConverterRegistry::Instance().FindOutputConverter("keyword.result"); ASSERT_NE(input, nullptr); ASSERT_NE(output, nullptr); char bytes[] = "query"; @@ -1095,27 +1090,26 @@ TEST_F(AdapterContractSecurityTest, InputLengthLimitsStayUnchanged) { }; // 数值期望与实现中的常量保持独立。 const Boundary boundaries[] = { - {"keyword.plain.operator.v1", "sentence_text", 65536, + {"keyword.plain", "sentence_text", 65536, "sentence_text length exceeds 64 KiB limit"}, - {"text.plain.operator.v1", "sentence_text", 65536, + {"text.plain", "sentence_text", 65536, "sentence_text length exceeds 64 KiB limit"}, - {"translate.json.operator.v1", "sentence_text", 65536, + {"translate.json", "sentence_text", 65536, "sentence_text length exceeds 64 KiB limit"}, - {"audit.plain.operator.v1", "audit_in.user_text", 65536, + {"audit.plain", "audit_in.user_text", 65536, "user_text length exceeds limit"}, - {"doc_query.plain.operator.v1", "doc_in.query_text", 65536, + {"doc_query.plain", "doc_in.query_text", 65536, "query_text length exceeds limit"}, - {"doc_query.plain.operator.v1", "doc_in.doc_text", 10485760, + {"doc_query.plain", "doc_in.doc_text", 10485760, "doc_text length exceeds limit"}, - {"image_query.plain.operator.v1", "frame.image_uri", 4096, + {"image_query.plain", "frame.image_uri", 4096, "image_uri length exceeds limit"}, - {"image_query.plain.operator.v1", "string", 65536, - "query length exceeds limit"}, - {"rerank.plain.operator.v1", "rerank_in.query_text", 65536, + {"image_query.plain", "string", 65536, "query length exceeds limit"}, + {"rerank.plain", "rerank_in.query_text", 65536, "query_text length exceeds limit"}, - {"rerank.plain.operator.v1", "rerank_in.candidate_passages", 65536, + {"rerank.plain", "rerank_in.candidate_passages", 65536, "candidate passage length exceeds limit"}, - {"rerank.plain.operator.v1", "rerank_in.candidate_count", 8, + {"rerank.plain", "rerank_in.candidate_count", 8, "candidate_count out of valid range [1, 8]"}, }; for (const auto& boundary : boundaries) { @@ -1130,7 +1124,7 @@ TEST_F(AdapterContractSecurityTest, InputLengthLimitsStayUnchanged) { for (size_t extra : {0U, 1U}) { SCOPED_TRACE(id + " " + field + " +" + std::to_string(extra)); std::string text(boundary.limit + extra, 'x'); - if (id == "translate.json.operator.v1") { + if (id == "translate.json") { text = "{\"query\":\"" + std::string(text.size() - 12, 'x') + "\"}"; } CompanyString large{static_cast(text.size()), text.data()}; @@ -1158,16 +1152,15 @@ TEST_F(AdapterContractSecurityTest, InputLengthLimitsStayUnchanged) { view.count = 1; for (const auto& slot : converter->external_slots) view.slot_types[slot.slot_name] = slot.type_id; - if (id == "keyword.plain.operator.v1") + if (id == "keyword.plain") view.slots["keyword_in"] = BorrowInputForTest({&keyword}); - else if (id == "text.plain.operator.v1" || - id == "translate.json.operator.v1") + else if (id == "text.plain" || id == "translate.json") view.slots["entity_in"] = BorrowInputForTest({&entity}); - else if (id == "audit.plain.operator.v1") + else if (id == "audit.plain") view.slots["audit_in"] = BorrowInputForTest({&audit}); - else if (id == "doc_query.plain.operator.v1") + else if (id == "doc_query.plain") view.slots["doc_in"] = BorrowInputForTest({&doc}); - else if (id == "image_query.plain.operator.v1") { + else if (id == "image_query.plain") { view.slots["frame"] = BorrowInputForTest({&frame}); view.slots["string"] = BorrowInputForTest({field == "string" ? &large : &small}); @@ -1284,16 +1277,15 @@ TEST_F(AdapterContractSecurityTest, OperatorInputLimitsStayUnchanged) { NamedIoBatch inputs(1), outputs(1); inputs[0]["test.rerank_in"] = MakeBorrowedOperatorInput(&rerank); outputs[0]["test.rerank_out"] = nullptr; - check( - conf.c_str(), inputs, outputs, -3, - length == 65537 - ? "DecodeInput failed for rerank.plain.operator.v1: " - "[AdapterStatus] Error -3 in Adapter [rerank.plain.operator.v1] " - "at sample [0] field `rerank_in.candidate_passages`: candidate " - "passage length exceeds limit" - : "Validation failed for input key test.rerank_in: " - "candidate_passages[0] length 10485761 exceeds max limit " - "10485760"); + check(conf.c_str(), inputs, outputs, -3, + length == 65537 + ? "DecodeInput failed for rerank.plain: " + "[AdapterStatus] Error -3 in Adapter [rerank.plain] " + "at sample [0] field `rerank_in.candidate_passages`: candidate " + "passage length exceeds limit" + : "Validation failed for input key test.rerank_in: " + "candidate_passages[0] length 10485761 exceeds max limit " + "10485760"); } for (size_t length : {4096U, 4097U}) { @@ -1333,7 +1325,7 @@ TEST_F(AdapterContractSecurityTest, // 关键词和规则命中、类型化规则常量、正则捕获以及默认命中 // 都会进入外部 JSON 响应。 const nlohmann::json pipeline = nlohmann::json::parse(R"j({ - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [{ "id": "rules", "node_type": "TextRuleMatchNode", @@ -1412,7 +1404,7 @@ TEST_F(AdapterContractSecurityTest, CreateAndExecutionFailuresUseStageCodes) { } } cleanup{directory}; nlohmann::json pipeline = nlohmann::json::parse(R"j({ - "deployment": {"io": {"io_binding": "entity_extract.operator.v1"}}, + "deployment": {"io": {"io_binding": "entity_extract"}}, "models": [{"model_id": "llm", "model_type": "qwen_causal_lm", "backend": "test_causal_lm_backend", "model_path": "neutral-llm.fixture", diff --git a/tests/contract/abi/test_operator_safety.cpp b/tests/contract/abi/test_operator_safety.cpp index 63a7c1a8..deb4796e 100644 --- a/tests/contract/abi/test_operator_safety.cpp +++ b/tests/contract/abi/test_operator_safety.cpp @@ -312,17 +312,16 @@ TEST_F(OperatorSafetyTest, // 6. 测试 IoBinding 注册冲突防护与定义机器可读性 TEST_F(OperatorSafetyTest, IoBindingRegistryConflictDetectionAndDescriptor) { auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); - const auto* binding = registry.FindBinding("keyword_match.operator.v1"); + const auto* binding = registry.FindBinding("keyword_match"); ASSERT_NE(binding, nullptr); - EXPECT_EQ(binding->binding_id, "keyword_match.operator.v1"); EXPECT_EQ(binding->biz_name, "keyword_match"); EXPECT_GT(binding->max_batch_size, 0); // 测试重复 binding 注册拦截 bool reg_dup_ret = registry.RegisterBinding(*binding); - EXPECT_FALSE(reg_dup_ret) << "Duplicate binding_id registration must fail"; + EXPECT_FALSE(reg_dup_ret) << "Duplicate biz binding registration must fail"; registry.ResetConflictForTesting(); } @@ -360,37 +359,45 @@ TEST_F(OperatorSafetyTest, UnknownAndUnregisteredBindingRejectionInCreate) { EXPECT_NE(ret, 0); EXPECT_EQ(handle, nullptr); - // 2) 传入缺失 io_binding 的配置 - std::string bad_cfg = "./results/test_missing_binding.conf"; - std::filesystem::create_directories("./results"); - { - std::ofstream ofs(bad_cfg); - ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json"}})"; - } - param.cfg_file_name = bad_cfg.c_str(); - ret = op.Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); - - // 3) 传入未知 io_binding - { - std::ofstream ofs(bad_cfg); - ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json", "io_binding": "unknown.binding.v999"}})"; - } - ret = op.Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); + // 2) 缺失 io_binding、3) 未知业务名;有效业务名作为对照可以创建。 + const auto directory = + std::filesystem::temp_directory_path() / + ("edgeflow-io-binding-" + + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count())); + std::filesystem::create_directory(directory); + std::ifstream source("configs/pipeline_keyword_match_rules.json"); + ASSERT_TRUE(source.is_open()); + nlohmann::json pipeline; + source >> pipeline; + std::ofstream(directory / "pipeline.conf") + << nlohmann::json({{"pipe_path", "pipeline.json"}}).dump(); + const std::string model_path = directory.string(); + param.model_path = model_path.c_str(); + param.cfg_file_name = "pipeline.conf"; + param.max_frame_depth = 8; + const auto create_with = [&](const nlohmann::json& document) { + std::ofstream(directory / "pipeline.json") << document.dump(); + void* created = nullptr; + const int code = op.Create(&created, ¶m); + if (created) op.Destroy(created); + return code; + }; + + auto missing = pipeline; + missing["deployment"]["io"].erase("io_binding"); + EXPECT_NE(create_with(missing), 0); + EXPECT_NE(std::string(GetOperatorLastError()).find("io_binding"), + std::string::npos); - // 4) 传入旧 cabi 绑定 - { - std::ofstream ofs(bad_cfg); - ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json", "io_binding": "keyword_match.cabi.v1"}})"; - } - ret = op.Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); + auto unknown = pipeline; + unknown["deployment"]["io"]["io_binding"] = "unknown_biz"; + EXPECT_NE(create_with(unknown), 0); + EXPECT_NE(std::string(GetOperatorLastError()).find("unknown_biz"), + std::string::npos); - std::filesystem::remove(bad_cfg); + EXPECT_EQ(create_with(pipeline), 0) << GetOperatorLastError(); + std::filesystem::remove_all(directory); } // 9. 测试 Registry 冲突 fail-closed 导致 Init 失败 @@ -403,7 +410,7 @@ TEST_F(OperatorSafetyTest, FailClosedRegistryConflictAndInitFailure) { EXPECT_EQ(op.Init(), 0); // 注册冲突(重复注册 binding) - const auto* binding = registry.FindBinding("keyword_match.operator.v1"); + const auto* binding = registry.FindBinding("keyword_match"); ASSERT_NE(binding, nullptr); bool reg_ret = registry.RegisterBinding(*binding); EXPECT_FALSE(reg_ret); @@ -454,10 +461,9 @@ TEST_F(OperatorSafetyTest, AdapterDescriptorMaxBatchSizeEnforcement) { TEST_F(OperatorSafetyTest, BindingBatchLimitOverridesLargerPoolAndConverters) { auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); - const auto* original = registry.FindBinding("keyword_match.operator.v1"); + const auto* original = registry.FindBinding("keyword_match"); ASSERT_NE(original, nullptr); auto binding = *original; - binding.binding_id = "test.keyword_match.single.operator.v1"; binding.max_batch_size = 1; const auto directory = @@ -490,7 +496,7 @@ TEST_F(OperatorSafetyTest, BindingBatchLimitOverridesLargerPoolAndConverters) { ASSERT_TRUE(source.is_open()); nlohmann::json pipeline; source >> pipeline; - pipeline["deployment"]["io"]["io_binding"] = binding.binding_id; + pipeline["deployment"]["io"]["io_binding"] = binding.biz_name; std::ofstream(directory / "pipeline.json") << pipeline.dump(); std::ofstream(directory / "pipeline.conf") << nlohmann::json({{"pipe_path", "pipeline.json"}}).dump(); @@ -607,7 +613,7 @@ TEST_F(OperatorSafetyTest, SameHandleConcurrentProcessAndQuiescedDestroy) { TEST_F(OperatorSafetyTest, EntityFailureSampleSentinelValues) { const auto* out_conv = llm_edgeflow::IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.operator.v1"); + "document.structured"); ASSERT_NE(out_conv, nullptr); llm_edgeflow::AlgContext ctx; diff --git a/tests/contract/catalog/test_catalog_contract_ssot.cpp b/tests/contract/catalog/test_catalog_contract_ssot.cpp index 0a133454..1a14cf41 100644 --- a/tests/contract/catalog/test_catalog_contract_ssot.cpp +++ b/tests/contract/catalog/test_catalog_contract_ssot.cpp @@ -308,7 +308,7 @@ TEST_F(CatalogContractSsotTest, IoCatalogExportsKeywordSlotNamesAndTypes) { ASSERT_EQ(catalog.at("output_converters").size(), 1U); const auto& input = catalog.at("input_converters").at(0); - EXPECT_EQ(input.at("converter_id"), "keyword.plain.operator.v1"); + EXPECT_EQ(input.at("converter_id"), "keyword.plain"); EXPECT_EQ(input.at("schema_id"), "text.plain.request"); EXPECT_EQ(input.at("external_type"), "CompanyOperatorKeywordInput"); ASSERT_EQ(input.at("external_slots").size(), 1U); @@ -322,7 +322,7 @@ TEST_F(CatalogContractSsotTest, IoCatalogExportsKeywordSlotNamesAndTypes) { EXPECT_EQ(input_slot.at("capacity_fields"), nlohmann::json::array()); const auto& output = catalog.at("output_converters").at(0); - EXPECT_EQ(output.at("converter_id"), "keyword.result.operator.v1"); + EXPECT_EQ(output.at("converter_id"), "keyword.result"); EXPECT_EQ(output.at("schema_id"), "keyword.result.response"); EXPECT_EQ(output.at("external_type"), "CompanyOperatorKeywordOutput"); ASSERT_EQ(output.at("external_slots").size(), 1U); @@ -356,10 +356,8 @@ TEST_F(CatalogContractSsotTest, } } scoped{registry.AllInputConverters(), registry.AllOutputConverters()}; - const auto* original_input = - registry.FindInputConverter("keyword.plain.operator.v1"); - const auto* original_output = - registry.FindOutputConverter("keyword.result.operator.v1"); + const auto* original_input = registry.FindInputConverter("keyword.plain"); + const auto* original_output = registry.FindOutputConverter("keyword.result"); ASSERT_NE(original_input, nullptr); ASSERT_NE(original_output, nullptr); auto input = *original_input; diff --git a/tests/fixtures/effects/entity_mock_exact.json b/tests/fixtures/effects/entity_mock_exact.json index 6842053d..341e9f9d 100644 --- a/tests/fixtures/effects/entity_mock_exact.json +++ b/tests/fixtures/effects/entity_mock_exact.json @@ -1,7 +1,7 @@ { "schema_version": 1, "name": "entity-neutral-model-contract", - "io_binding": "entity_extract.operator.v1", + "io_binding": "entity_extract", "dataset": "../../../data/corpus_entity_extract.txt", "minimum_pass_rate": 1.0, "samples": [ diff --git a/tests/fixtures/effects/keyword_exact.json b/tests/fixtures/effects/keyword_exact.json index ac1178dc..aac28cf9 100644 --- a/tests/fixtures/effects/keyword_exact.json +++ b/tests/fixtures/effects/keyword_exact.json @@ -1,7 +1,7 @@ { "schema_version": 1, "name": "keyword-selected-config", - "io_binding": "keyword_match.operator.v1", + "io_binding": "keyword_match", "dataset": "keyword_inputs.txt", "minimum_pass_rate": 1.0, "samples": [ diff --git a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json index ad8b6390..0e1367df 100644 --- a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json +++ b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "cross_rerank.operator.v1" + "io_binding": "cross_rerank" } }, "models": [ diff --git a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json index dbce9fab..89a91318 100644 --- a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json +++ b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json @@ -1,7 +1,7 @@ { "deployment": { "io": { - "io_binding": "cross_rerank.operator.v1" + "io_binding": "cross_rerank" } }, "models": [ diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 5787c22d..4efc4455 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -1289,7 +1289,7 @@ TEST_F(OperatorApiTest, OutputsConfigValidationFailClosed) { "schema_version": 1, "data": { "pipe_path": "pipeline_keyword_match_rules.json", - "io_binding": "keyword_match.operator.v1" + "io_binding": "keyword_match" } })"; } @@ -2205,7 +2205,7 @@ TEST_F(OperatorApiTest, VariableResultsUsePoolCapacityAndRollbackOnFailure) { nlohmann::json pipeline = { {"deployment", {{"io", - {{"io_binding", "keyword_match.operator.v1"}, + {{"io_binding", "keyword_match"}, {"out_mem", {{"keyword_out", {{"capacities", {{"match_result_json", capacity}}}}}}}}}}}, @@ -2419,7 +2419,7 @@ int EncodeNestedOutput(AlgContext* context, const OutputEncodeOptions& options, const bool g_reg_nested_output_components = []() { BizDefinition bdef; - bdef.biz_name = "test_nested_output_v1"; + bdef.biz_name = "test_nested_output"; bdef.ingress = { BizPortDefinition("input_sentences", "TextBatch", true, "1:1")}; bdef.egress = { @@ -2429,7 +2429,7 @@ const bool g_reg_nested_output_components = []() { } OutputConverterDefinition odef; - odef.converter_id = "test_nested_output.operator.v1"; + odef.converter_id = "test_nested_output"; odef.max_batch_size = 64; odef.schema_id = "test_nested_output"; @@ -2444,11 +2444,10 @@ const bool g_reg_nested_output_components = []() { IoConverterRegistry::Instance().RegisterOutputConverter(odef); IoBindingDefinition bind; - bind.binding_id = "nested_output_test.operator.v1"; - bind.biz_name = "test_nested_output_v1"; + bind.biz_name = "test_nested_output"; - bind.input_converter_id = "keyword.plain.operator.v1"; - bind.output_converter_id = "test_nested_output.operator.v1"; + bind.input_converter_id = "keyword.plain"; + bind.output_converter_id = "test_nested_output"; bind.max_batch_size = 64; IoBindingRegistry::Instance().RegisterBinding(bind); return true; @@ -2475,7 +2474,7 @@ nlohmann::json NestedOutputPipelineJson(bool alternate = false) { pipeline.erase("biz_name"); pipeline["deployment"] = { {"io", - {{"io_binding", "nested_output_test.operator.v1"}, + {{"io_binding", "test_nested_output"}, {"out_mem", NestedOutputAllocations(alternate)}}}}; return pipeline; } @@ -2517,16 +2516,16 @@ void ExpectNestedResult(const std::shared_ptr& value, uint64_t request_id, TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { using namespace llm_edgeflow; using namespace llm_edgeflow::test_support; - const auto* production = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.operator.v1"); + const auto* production = + IoConverterRegistry::Instance().FindInputConverter("keyword.plain"); ASSERT_NE(production, nullptr); auto input = *production; - input.converter_id = "test_partial_request_ids.operator.v1"; + input.converter_id = "test_partial_request_ids"; input.decode_fn = [](const ExternalInputBatchView& source, const InputDecodeOptions& options, AlgContext* context, AdapterStatus* status) { - const auto* converter = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindInputConverter("keyword.plain"); const int ret = converter->decode_fn(source, options, context, status); if (ret == 0) options.request_ids->resize(1); return ret; @@ -2534,8 +2533,8 @@ TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { // 注册表是进程级全局的;只注册一次,以便测试可重复运行。 if (!IoConverterRegistry::Instance().FindInputConverter(input.converter_id)) ASSERT_TRUE(IoConverterRegistry::Instance().RegisterInputConverter(input)); - const auto* nested = IoBindingRegistry::Instance().FindBinding( - "nested_output_test.operator.v1"); + const auto* nested = + IoBindingRegistry::Instance().FindBinding("test_nested_output"); ASSERT_NE(nested, nullptr); // 每个业务只有一个 binding,测试 binding 使用复制出的独立业务。 auto biz = *PipelineCatalog::FindBiz(nested->biz_name); @@ -2544,15 +2543,14 @@ TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(biz)); } auto binding = *nested; - binding.binding_id = "request_id_count_test.operator.v1"; binding.biz_name = biz.biz_name; binding.input_converter_id = input.converter_id; - if (!IoBindingRegistry::Instance().FindBinding(binding.binding_id)) + if (!IoBindingRegistry::Instance().FindBinding(binding.biz_name)) ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); ScopedTempDirectory temp; auto pipeline = NestedOutputPipelineJson(); - pipeline["deployment"]["io"]["io_binding"] = binding.binding_id; + pipeline["deployment"]["io"]["io_binding"] = binding.biz_name; std::ofstream(temp.path() / "pipeline.json") << pipeline; std::ofstream(temp.path() / "pipeline.conf") << nlohmann::json{{"pipe_path", "pipeline.json"}}; @@ -2576,7 +2574,7 @@ TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { EXPECT_EQ(ops_.Process(handle.get(), inputs, outputs), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_STREQ(GetOperatorLastError(), - "DecodeInput for test_partial_request_ids.operator.v1 recorded " + "DecodeInput for test_partial_request_ids recorded " "1 request ids for 2 inputs"); for (const auto& frame : outputs) { EXPECT_EQ(frame.at("test.result"), nullptr); @@ -2758,7 +2756,7 @@ TEST_F(OperatorApiTest, MissingOutputMemoryUsesRegisteredNestedDefaults) { sizeof(error)), 0) << error; - EXPECT_EQ(biz, "test_nested_output_v1"); + EXPECT_EQ(biz, "test_nested_output"); CreateParam param{}; param.model_path = root.c_str(); param.cfg_file_name = "pipeline.conf"; @@ -2937,7 +2935,7 @@ TEST_F(OperatorApiTest, SharedCarrierDoesNotMergePayloadSchema) { // 翻译 Adapter 要求带 "query" 的 JSON 对象,因此拒绝纯文本 const auto* translate_in_conv = llm_edgeflow::IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + "translate.json"); ASSERT_NE(translate_in_conv, nullptr); CompanyOperatorEntityInput c_in_plain{50001, &cs_plain}; llm_edgeflow::AlgContext ctx; diff --git a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp index 033786aa..0fba0ac1 100644 --- a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp +++ b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp @@ -543,9 +543,9 @@ TEST(PipelineValidatorTest, ReportsConfigAndCapabilityErrors) { } static nlohmann::json MakeSyntheticDeploymentDocForTest( - const nlohmann::json& pipeline_json, const std::string& binding_id) { + const nlohmann::json& pipeline_json, const std::string& io_binding) { nlohmann::json allocations = nlohmann::json::object(); - const auto* binding = IoBindingRegistry::Instance().FindBinding(binding_id); + const auto* binding = IoBindingRegistry::Instance().FindBinding(io_binding); if (binding) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( binding->output_converter_id); @@ -580,7 +580,7 @@ static nlohmann::json MakeSyntheticDeploymentDocForTest( nlohmann::json synthetic = pipeline_json; synthetic.erase("biz_name"); synthetic["deployment"] = { - {"io", {{"io_binding", binding_id}, {"out_mem", allocations}}}}; + {"io", {{"io_binding", io_binding}, {"out_mem", allocations}}}}; return synthetic; } @@ -667,27 +667,15 @@ TEST(PipelineValidatorTest, TableDrivenParityMatrix) { // 3. 共享运行时必须在实例化前失败,并在其内部 C++ 错误边界中 // 保留主结构化诊断。 std::string biz = config.value("biz_name", ""); - std::string binding_id; - for (const auto& b : IoBindingRegistry::Instance().AllBindings()) { - if (b.biz_name == biz) { - binding_id = b.binding_id; - break; - } - } - if (binding_id.empty()) { - binding_id = "test_synthetic." + biz + ".operator.v1"; - if (!IoBindingRegistry::Instance().FindBinding(binding_id)) { - IoBindingDefinition synth_b; - synth_b.binding_id = binding_id; - synth_b.biz_name = biz; - synth_b.input_converter_id = "keyword.plain.operator.v1"; - synth_b.output_converter_id = "keyword.result.operator.v1"; - synth_b.max_batch_size = 64; - IoBindingRegistry::Instance().RegisterBinding(synth_b); - } + if (!IoBindingRegistry::Instance().FindBinding(biz)) { + IoBindingDefinition synth_b; + synth_b.biz_name = biz; + synth_b.input_converter_id = "keyword.plain"; + synth_b.output_converter_id = "keyword.result"; + synth_b.max_batch_size = 64; + IoBindingRegistry::Instance().RegisterBinding(synth_b); } - nlohmann::json dep_config = - MakeSyntheticDeploymentDocForTest(config, binding_id); + nlohmann::json dep_config = MakeSyntheticDeploymentDocForTest(config, biz); std::unique_ptr io_plan; std::string resolve_error; DeploymentDiagnostic resolve_diagnostic; diff --git a/tests/support/control_test_utils.h b/tests/support/control_test_utils.h index e5e6ec7a..2a243c48 100644 --- a/tests/support/control_test_utils.h +++ b/tests/support/control_test_utils.h @@ -12,7 +12,7 @@ inline void WriteControlTestPipeline(const std::filesystem::path& directory) { const nlohmann::json pipeline = { {"deployment", {{"io", - {{"io_binding", "keyword_match.operator.v1"}, + {{"io_binding", "keyword_match"}, {"out_mem", {{"keyword_out", {{"meta_num", 0}, diff --git a/tests/tooling/generate_scaffold_fixtures.py b/tests/tooling/generate_scaffold_fixtures.py index d459fe7b..e451e8d9 100644 --- a/tests/tooling/generate_scaffold_fixtures.py +++ b/tests/tooling/generate_scaffold_fixtures.py @@ -102,7 +102,7 @@ def apply_documented_text_functions(code): ASSERT_EQ(IoBindingResolver::ResolveFromConfig( parsed, directory.string(), &plan, &error), 0) << error; ASSERT_NE(plan, nullptr); - EXPECT_EQ(plan->binding.binding_id, "keyword_match.operator.v1"); + EXPECT_EQ(plan->binding.biz_name, "keyword_match"); } } // namespace llm_edgeflow ''') diff --git a/tests/tooling/studio_browser_test.mjs b/tests/tooling/studio_browser_test.mjs index 89fc2b97..119b09bb 100644 --- a/tests/tooling/studio_browser_test.mjs +++ b/tests/tooling/studio_browser_test.mjs @@ -41,14 +41,14 @@ try { assert.equal(await page.locator("#newEntryButton").isVisible(), true); await page.click("#newEntryButton"); assert.equal(await page.locator("#newButton").isVisible(), true, "one click exposes creation"); - await page.selectOption("#bindingSelect", "keyword_match.operator.v1"); + await page.selectOption("#bindingSelect", "keyword_match"); await page.selectOption("#cloneProfile", "keyword_match_rules"); await page.click("#newButton"); await page.waitForFunction(() => document.querySelectorAll('.node').length === 3 && !document.querySelector('#newButton').disabled); - assert.equal((await json()).deployment.io.io_binding, "keyword_match.operator.v1"); + assert.equal((await json()).deployment.io.io_binding, "keyword_match"); assert.equal(Object.hasOwn(await json(), "biz_name"), false); assert.deepEqual(initializationRequests.at(-1), { - io_binding: "keyword_match.operator.v1", profile: "keyword_match_rules", empty: false, + io_binding: "keyword_match", profile: "keyword_match_rules", empty: false, }); page.once("dialog", dialog => dialog.accept()); await open("pipeline_browser.json"); @@ -79,10 +79,9 @@ try { await page.click("#openRunButton"); await page.locator("#bizContract > summary").click(); for (const [label, expected] of [ - ["I/O 契约 · io_binding", ["keyword_match.operator.v1"]], - ["Binding ID", ["keyword_match.operator.v1"]], - ["输入 Converter", ["keyword.plain.operator.v1"]], - ["输出 Converter", ["keyword.result.operator.v1"]], + ["I/O 契约 · io_binding", ["keyword_match"]], + ["输入 Converter", ["keyword.plain"]], + ["输出 Converter", ["keyword.result"]], ["输入协议 · schema_id", ["text.plain.request"]], ["输出协议 · schema_id", ["keyword.result.response"]], ["输入逻辑槽 · slot_name", ["keyword_in"]], @@ -93,8 +92,7 @@ try { ]) assert.deepEqual(await contractValues(label), expected, label); await open("pipeline_browser_multi.json"); await page.click("#openRunButton"); - assert.deepEqual(await contractValues("I/O 契约 · io_binding"), ["doc_qa.operator.v1"]); - assert.deepEqual(await contractValues("Binding ID"), ["doc_qa.operator.v1"]); + assert.deepEqual(await contractValues("I/O 契约 · io_binding"), ["doc_qa"]); assert.doesNotMatch(await page.locator("#bizContractFields").textContent(), /keyword/, "Changing documents must replace every previous contract field"); await page.setViewportSize({ width: 390, height: 844 }); @@ -109,7 +107,7 @@ try { "Contract labels and identifiers must wrap within their fields"); await screenshot("390-contract-details"); await page.setViewportSize({ width: 1366, height: 768 }); - await page.route("**/api/v1/catalog?io_binding=keyword_match.operator.v1", async route => { + await page.route("**/api/v1/catalog?io_binding=keyword_match", async route => { const response = await route.fetch(); const catalog = await response.json(); for (const converter of [...catalog.input_converters, ...catalog.output_converters]) { @@ -184,7 +182,7 @@ try { await page.waitForFunction(() => document.querySelector("#operationFeedback").textContent.includes("已保存")); const savedPipeline = JSON.parse(readFileSync(join(configRoot, "pipeline_browser.json"))); assert.deepEqual(savedPipeline.pipeline[0].config.categories, { SAVED_BROWSER: ["VIP"] }); - assert.equal(savedPipeline.deployment.io.io_binding, "keyword_match.operator.v1"); + assert.equal(savedPipeline.deployment.io.io_binding, "keyword_match"); assert.equal(Object.hasOwn(savedPipeline, "biz_name"), false); assert.equal(Object.hasOwn(savedPipeline.deployment.io, "output_allocations"), false); assert.match(await page.locator("#saveScope").textContent(), /pipeline_browser.json/); diff --git a/tests/tooling/studio_fix_workflow_test.mjs b/tests/tooling/studio_fix_workflow_test.mjs index 5244321c..1cbb87c3 100644 --- a/tests/tooling/studio_fix_workflow_test.mjs +++ b/tests/tooling/studio_fix_workflow_test.mjs @@ -77,7 +77,7 @@ async function load(name) { const app = await load("app.js"); await app.evaluate(); const { state, history, drafts, handleApplyFix, restoreHistory } = app.namespace; -const initial = { deployment: { io: { io_binding: "keyword_match.operator.v1" } }, models: [], pipeline: [], comment: "before" }; +const initial = { deployment: { io: { io_binding: "keyword_match" } }, models: [], pipeline: [], comment: "before" }; const patched = { ...initial, comment: "after" }; const fix = { id: "repair-1", title: "Repair", effect: "Replace comment", verification: "pipeline_valid", patch: [{ op: "replace", path: "/comment", value: "after" }] }; diff --git a/tests/tooling/studio_graph_test.mjs b/tests/tooling/studio_graph_test.mjs index e4494cc8..ec7b69ae 100644 --- a/tests/tooling/studio_graph_test.mjs +++ b/tests/tooling/studio_graph_test.mjs @@ -133,11 +133,11 @@ console.log("Studio graph geometry, routing and interaction checks passed"); // 都不得静默绑定到同名键。 const workbenchSource = readFileSync(new URL("../../tools/pipeline_studio/web/workbench.js", import.meta.url), "utf8"); const { graphDocument } = await import(`data:text/javascript;base64,${Buffer.from(workbenchSource).toString("base64")}`); -const catalog = { io_bindings: [{ binding_id: "example.binding", biz_name: "example" }], bizs: [{ biz_name: "example", ingress: [{ key: "request" }], egress: [] }], nodes: [ +const catalog = { io_bindings: [{ biz_name: "example" }], bizs: [{ biz_name: "example", ingress: [{ key: "request" }], egress: [] }], nodes: [ { node_type: "Producer", inputs: [], outputs: [{ key: "text" }, { key: "optional" }] }, { node_type: "Consumer", inputs: [{ key: "text", required: true }, { key: "optional", required: false }], outputs: [] }, ] }; -const pipelineDocument = { deployment: { io: { io_binding: "example.binding" } }, pipeline: [ +const pipelineDocument = { deployment: { io: { io_binding: "example" } }, pipeline: [ { id: "consumer", node_type: "Consumer", inputs: { text: "text" } }, { id: "producer", node_type: "Producer" }, { id: "missing", node_type: "Consumer", depends_on: ["producer"] }, diff --git a/tests/tooling/test_dev_recipe.py b/tests/tooling/test_dev_recipe.py index 2b5e6a9b..ada0bf75 100644 --- a/tests/tooling/test_dev_recipe.py +++ b/tests/tooling/test_dev_recipe.py @@ -229,7 +229,7 @@ def test_missing_outputs_deployment_uses_native_required_slot_defaults(self): report = self.prepare() self.assertTrue(report["ok"], report) generated = json.loads(self.target.read_text()) - self.assertEqual(generated["deployment"]["io"], {"io_binding": "keyword_match.operator.v1"}) + self.assertEqual(generated["deployment"]["io"], {"io_binding": "keyword_match"}) self.assertTrue(self.verify()["ok"]) def test_legacy_mem_que_deployment_rejected_as_missing_outputs(self): @@ -245,7 +245,7 @@ def test_unlabelled_or_duplicate_effects_rejected_before_generation(self): for invalid in ( dict(labelled, samples=[{"request_id": 20001, "expected": {"/status": 0}}]), dict(labelled, samples=[labelled["samples"][0], labelled["samples"][0]]), - dict(labelled, io_binding="entity_extract.operator.v1"), + dict(labelled, io_binding="entity_extract"), ): with self.subTest(spec=invalid): source.write_text(json.dumps(invalid)) diff --git a/tests/tooling/test_pipeline_studio.py b/tests/tooling/test_pipeline_studio.py index fc19de0a..bcfe262e 100644 --- a/tests/tooling/test_pipeline_studio.py +++ b/tests/tooling/test_pipeline_studio.py @@ -93,7 +93,7 @@ def test_rejects_paths_symlinks_and_invalid_pipeline_without_writing(self): with self.assertRaises(SHOW.StudioError) as link: self.service.open_pipeline("pipeline_link.json") self.assertEqual(link.exception.code, "SYMLINK_REJECTED") - invalid = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "pipeline": []} + invalid = {"deployment": {"io": {"io_binding": "keyword_match"}}, "pipeline": []} with self.assertRaises(SHOW.StudioError) as validation: self.service.save_pipeline("pipeline_invalid.json", invalid, None, True) self.assertEqual(validation.exception.code, "VALIDATION_FAILED") @@ -537,10 +537,10 @@ def test_native_deployment_rejection_rolls_back_the_pair(self): def test_profile_preserves_explicit_binding_selection(self): pipeline = copy.deepcopy(self.keyword) saved = self.service.save_solution("pipeline_profile_binding.json", pipeline, "keyword_match_rules") - self.assertEqual(saved["pipeline"]["deployment"]["io"]["io_binding"], "keyword_match.operator.v1") + self.assertEqual(saved["pipeline"]["deployment"]["io"]["io_binding"], "keyword_match") self.assertNotIn("biz_name", saved["pipeline"]) - self.assertEqual(saved["configuration"]["io_binding"], "keyword_match.operator.v1") - for binding in ("", "unknown.operator.v1", "entity_extract.operator.v1"): + self.assertEqual(saved["configuration"]["io_binding"], "keyword_match") + for binding in ("", "unknown_biz", "entity_extract"): with self.subTest(binding=binding): pipeline = copy.deepcopy(self.keyword) io_overrides(pipeline)["io_binding"] = binding @@ -657,8 +657,8 @@ def test_removed_dependency_repair_command_is_rejected_without_writing(self): def test_explicit_binding_uses_native_required_output_defaults(self): original = json.loads((ROOT / "configs/pipeline_keyword_match_rules.json").read_text()) original.pop("deployment", None) - variants = [{"io": {"io_binding": "keyword_match.operator.v1"}}, - {"io": {"io_binding": "keyword_match.operator.v1", "out_mem": {}}}] + variants = [{"io": {"io_binding": "keyword_match"}}, + {"io": {"io_binding": "keyword_match", "out_mem": {}}}] for deployment in variants: with self.subTest(deployment=deployment), tempfile.TemporaryDirectory() as directory: pipeline = copy.deepcopy(original) @@ -673,7 +673,7 @@ def test_explicit_binding_uses_native_required_output_defaults(self): code, resolved = self.command("validate-io", str(path / "pipeline.conf")) self.assertEqual(code, 0, resolved) self.assertEqual(resolved["binding"]["biz_name"], "keyword_match") - self.assertEqual(resolved["binding"]["binding_id"], "keyword_match.operator.v1") + self.assertNotIn("binding_id", resolved["binding"]) self.assertEqual(set(resolved["output_pools"]), {"keyword_out"}) self.assertEqual(resolved["output_pools"]["keyword_out"]["capacities"], {"match_result_json": 2047}) self.assertEqual((path / "pipeline.json").read_text(), before) @@ -703,7 +703,7 @@ def test_external_selector_is_required_and_legacy_selectors_are_rejected(self): code, result = self.command("validate", "--stdin", input_pipeline=pipeline) self.assertEqual(code, 1, result) self.assertEqual(result["diagnostics"][0]["path"], path) - code, result = self.command("catalog", "--io-binding", "unknown.binding") + code, result = self.command("catalog", "--io-binding", "unknown_biz") self.assertEqual(code, 1, result) self.assertEqual(result["diagnostics"][0]["code"], "UNKNOWN_IO_BINDING") for command in ("catalog", "init"): @@ -712,23 +712,23 @@ def test_external_selector_is_required_and_legacy_selectors_are_rejected(self): self.assertEqual(process.returncode, 2, process.stdout) def test_all_commands_return_versioned_json(self): - first_code, first = self.command("catalog", "--io-binding", "keyword_match.operator.v1") - second_code, second = self.command("catalog", "--io-binding", "keyword_match.operator.v1") + first_code, first = self.command("catalog", "--io-binding", "keyword_match") + second_code, second = self.command("catalog", "--io-binding", "keyword_match") self.assertEqual((first_code, first), (second_code, second)) self.assertTrue(first["nodes"]) self.assertTrue(first["profiles"]) - self.assertTrue(all(profile["io_binding"] == "keyword_match.operator.v1" for profile in first["profiles"])) + self.assertTrue(all(profile["io_binding"] == "keyword_match" for profile in first["profiles"])) self.assertTrue(all("biz" not in profile and "pipeline_biz" not in profile for profile in first["profiles"])) self.assertTrue(all("demo_biz" not in biz for biz in first["bizs"])) code, described = self.command("describe-node", "TextRuleMatchNode") self.assertEqual(code, 0) self.assertEqual(described["node_type"], "TextRuleMatchNode") code, initialized = self.command( - "init", "--io-binding", "keyword_match.operator.v1", "--empty" + "init", "--io-binding", "keyword_match", "--empty" ) self.assertEqual(code, 0) self.assertEqual(initialized["pipeline"]["pipeline"], []) - self.assertEqual(initialized["pipeline"]["deployment"]["io"]["io_binding"], "keyword_match.operator.v1") + self.assertEqual(initialized["pipeline"]["deployment"]["io"]["io_binding"], "keyword_match") self.assertNotIn("business_name", initialized["pipeline"]) rejected = subprocess.run( [str(PIPELINE_TOOL), "catalog", "--business", "keyword_match"], @@ -770,15 +770,15 @@ def test_catalog_profile_identity_does_not_require_model_validation(self): "unavailable_models": {"config": "pipeline.conf", "dataset": "unused.txt"} }})) (root / "pipeline.conf").write_text(json.dumps({"pipe_path": "pipeline.json"})) - pipeline = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + pipeline = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [{"model_type": "unregistered_in_this_build"}], "pipeline": []} (root / "pipeline.json").write_text(json.dumps(pipeline)) - process = subprocess.run([str(PIPELINE_TOOL), "catalog", "--io-binding", "keyword_match.operator.v1"], + process = subprocess.run([str(PIPELINE_TOOL), "catalog", "--io-binding", "keyword_match"], cwd=root, capture_output=True, text=True) self.assertEqual(process.returncode, 0, process.stderr) profiles = json.loads(process.stdout)["profiles"] self.assertEqual([p["name"] for p in profiles], ["unavailable_models"]) - self.assertEqual(profiles[0]["io_binding"], "keyword_match.operator.v1") + self.assertEqual(profiles[0]["io_binding"], "keyword_match") self.assertEqual(profiles[0]["biz_name"], "keyword_match") def test_invalid_profile_fields_and_shapes_are_rejected_by_all_consumers(self): @@ -793,7 +793,7 @@ def test_invalid_profile_fields_and_shapes_are_rejected_by_all_consumers(self): for profile, expected, field in cases: with self.subTest(profile=profile): profile_path.write_text(json.dumps({"schema_version": 2, "profiles": {"invalid": profile}})) - for args in (("catalog",), ("init", "--io-binding", "keyword_match.operator.v1", "--profile", "invalid")): + for args in (("catalog",), ("init", "--io-binding", "keyword_match", "--profile", "invalid")): process = subprocess.run([str(PIPELINE_TOOL), *args], cwd=root, capture_output=True, text=True) self.assertEqual(process.returncode, 1, process.stderr) diagnostic = json.loads(process.stdout)["diagnostics"][0] @@ -853,7 +853,7 @@ def test_describe_models_and_backends_require_one_argument(self): self.assertIn("Usage:", process.stderr) def test_init_raw_can_be_saved_and_validated_without_unwrapping(self): - args = ["init", "--io-binding", "keyword_match.operator.v1", "--profile", "keyword_match_rules"] + args = ["init", "--io-binding", "keyword_match", "--profile", "keyword_match_rules"] code, wrapped = self.command(*args) self.assertEqual(code, 0) raw = subprocess.run( @@ -872,12 +872,12 @@ def test_init_raw_can_be_saved_and_validated_without_unwrapping(self): code, validated = self.command("validate", str(saved)) self.assertEqual(code, 0, validated) empty = subprocess.run( - [str(PIPELINE_TOOL), "init", "--raw", "--io-binding", "keyword_match.operator.v1", "--empty"], + [str(PIPELINE_TOOL), "init", "--raw", "--io-binding", "keyword_match", "--empty"], text=True, capture_output=True, cwd=ROOT, check=False, ) self.assertEqual(empty.returncode, 0, empty.stderr) self.assertEqual(json.loads(empty.stdout), { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": [] + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [] }) def test_init_rejects_invalid_options(self): @@ -885,12 +885,12 @@ def test_init_rejects_invalid_options(self): ["--profile", "keyword_match_rules", "--empty"], ["--profile"], ["--profile", "--raw"], ["--unknown"], ["--raw", "--raw"], ["--empty", "--empty"], - ["--io-binding", "keyword_match.operator.v1"], + ["--io-binding", "keyword_match"], ["--profile", "keyword_match_rules", "--profile", "keyword_match_rules"], ): with self.subTest(options=options): process = subprocess.run( - [str(PIPELINE_TOOL), "init", "--io-binding", "keyword_match.operator.v1", *options], + [str(PIPELINE_TOOL), "init", "--io-binding", "keyword_match", *options], text=True, capture_output=True, cwd=ROOT, check=False, ) self.assertEqual(process.returncode, 2) @@ -905,7 +905,7 @@ def test_resolve_conf_exposes_model_sources_defaults_and_native_pool_errors(self self.assertEqual(code, 0, report) configuration = report["configuration"] self.assertEqual(configuration["biz_name"], "entity_extract") - self.assertEqual(configuration["io_binding"], "entity_extract.operator.v1") + self.assertEqual(configuration["io_binding"], "entity_extract") self.assertEqual(configuration["effective_frame_depth"], 1) self.assertEqual(configuration["effective_process_batch_limit"], 1) self.assertEqual(configuration["max_frame_depth_limit"], 1024) @@ -918,7 +918,7 @@ def test_resolve_conf_exposes_model_sources_defaults_and_native_pool_errors(self self.assertEqual(code, 1) self.assertEqual(oversized["diagnostics"][0]["message"], "max_frame_depth (1025) exceeds hard limit 1024") self.assertNotIn("biz_name", configuration["effective_pipeline"]) - self.assertEqual(configuration["effective_pipeline"]["deployment"]["io"]["io_binding"], "entity_extract.operator.v1") + self.assertEqual(configuration["effective_pipeline"]["deployment"]["io"]["io_binding"], "entity_extract") self.assertEqual(configuration["effective_pipeline"]["models"][0]["model_path"], str(ROOT / "demo/fixtures/mock/artifacts/neutral-llm.fixture")) self.assertEqual(configuration["conf_path"], str(conf_path)) self.assertEqual(configuration["model_paths"], [{ @@ -1085,12 +1085,12 @@ def test_cli_unknown_binding_unknown_root_and_malformed_binding(self): ) # 1. 未知 io_binding doc1 = copy.deepcopy(pipeline) - io_overrides(doc1)["io_binding"] = "nonexistent.binding.v99" + io_overrides(doc1)["io_binding"] = "nonexistent_biz" # 2. 即使存在 binding,已移除的根选择器也会被拒绝。 doc2 = copy.deepcopy(pipeline) doc2["biz_name"] = "unmatched_biz_name" - io_overrides(doc2)["io_binding"] = "entity_extract.operator.v1" + io_overrides(doc2)["io_binding"] = "entity_extract" # 3. binding 必须是非空字符串。 doc3 = copy.deepcopy(pipeline) @@ -1192,7 +1192,7 @@ def test_cli_edit_invalid_deployment_validation_policy(self): (ROOT / "demo/fixtures/mock/pipeline_entity_extract.json").read_text() ) invalid_doc = copy.deepcopy(pipeline) - io_overrides(invalid_doc)["io_binding"] = "invalid_binding_id" + io_overrides(invalid_doc)["io_binding"] = "invalid_biz" op = { "kind": "add_node", @@ -1412,7 +1412,7 @@ def test_startup_endpoints_load_rerank_pipeline_and_refresh_saved_list(self): managed.write_text(json.dumps(pipeline)) self.service.initial_document = self.service.open_pipeline(path.name) for endpoint in ("/catalog", "/profiles", "/pipelines", "/assets", "/initial", - "/catalog?io_binding=doc_qa.operator.v1"): + "/catalog?io_binding=doc_qa"): with self.subTest(endpoint=endpoint), urllib.request.urlopen(self.base + endpoint, timeout=10) as response: payload = json.load(response) self.assertTrue(payload["ok"]) @@ -1448,8 +1448,8 @@ def test_web_api_preserves_proxy_paths_and_reports_non_json_responses(self): location.href = base + page; assert.equal((await api("/pipelines")).pipelines[0].filename, "pipeline_test.json"); assert.equal(requested, base + "api/v1/pipelines"); - await api("/catalog?io_binding=doc_qa.operator.v1"); - assert.equal(requested, base + "api/v1/catalog?io_binding=doc_qa.operator.v1"); + await api("/catalog?io_binding=doc_qa"); + assert.equal(requested, base + "api/v1/catalog?io_binding=doc_qa"); } } body = JSON.stringify({ ok: false, error: { code: "INVALID_JSON", message: "invalid pipeline" } }); @@ -1634,7 +1634,7 @@ def test_readonly_graph_matches_native_validator_and_model_forms(self): const code = readFileSync(process.argv[1], 'utf8'); const w = await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`); const catalog = JSON.parse(readFileSync(0, 'utf8')); -const pipeline = {deployment:{io:{io_binding:'keyword_match.operator.v1'}}, models:[], pipeline:[ +const pipeline = {deployment:{io:{io_binding:'keyword_match'}}, models:[], pipeline:[ {id:'template', node_type:'TextTemplateNode', depends_on:[], config:{template:'{{primary}}'}, inputs:{primary:'input_sentences'},outputs:{text:'template_text'}}, {id:'rule', node_type:'TextRuleMatchNode', depends_on:['template'], config:{}, inputs:{text:'template_text'},outputs:{matches:'rule_matches'}} ]}; @@ -1659,12 +1659,12 @@ def test_readonly_graph_matches_native_validator_and_model_forms(self): w.upsertModel(models,catalog,'',{model_id:'embed',model_type:modelDef.model_type,backend:backend.backend_type,model_path:'embed.onnx',model_config:w.schemaDefaults(modelDef.config_fields),backend_config:w.schemaDefaults(backend.config_fields)}); assert.equal(models.deployment,undefined,'adding a model must not invent deployment overrides'); assert.equal(Object.hasOwn(models.models[0], "capability"), false); -models.deployment = {io:{io_binding:'doc_qa.operator.v1'}}; +models.deployment = {io:{io_binding:'doc_qa'}}; models.pipeline.push({id:'embed_node',node_type:'TextEmbeddingNode',config:{bind_model:'embed'}}); w.upsertModel(models,catalog,'embed',{...models.models[0],model_id:'renamed'}); assert.equal(models.pipeline[0].config.bind_model,'renamed'); assert.equal(models.models[0].model_path,'embed.onnx'); -assert.deepEqual(models.deployment,{io:{io_binding:'doc_qa.operator.v1'}}); +assert.deepEqual(models.deployment,{io:{io_binding:'doc_qa'}}); assert.throws(()=>w.removeModel(models,catalog,'renamed'), /使用/); assert.throws(()=>w.upsertModel(models,catalog,'',{...models.models[0],backend:'llama_cpp'}), /不兼容/); } @@ -1695,9 +1695,10 @@ def test_invalid_fixtures_table_driven_parity_matrix(self): with self.subTest(case=case["name"]): pipeline = copy.deepcopy(case["pipeline"]) _, catalog = PipelineCliTest.command(self, "catalog") - bindings = {b["biz_name"]: b["binding_id"] for b in catalog["io_bindings"]} + registered = {b["biz_name"] for b in catalog["io_bindings"]} neutral_biz = pipeline.pop("biz_name") - pipeline["deployment"] = {"io": {"io_binding": bindings.get(neutral_biz, "unknown.binding")}} + io_binding = neutral_biz if neutral_biz in registered else "unknown_biz" + pipeline["deployment"] = {"io": {"io_binding": io_binding}} if case["primary_code"] == "UNKNOWN_BIZ": case = {**case, "primary_code": "UNKNOWN_IO_BINDING", "primary_path": "/deployment/io/io_binding", "required_codes": ["UNKNOWN_IO_BINDING"]} @@ -1803,12 +1804,12 @@ def test_run_conf_preserves_binding_override_without_inference(self): selection = SHOW.SELECTION with tempfile.TemporaryDirectory() as directory: root = Path(directory) - for binding in ("keyword_match.operator.v1", "new_contract.operator.v1"): + for binding in ("keyword_match", "new_contract"): pipeline = {"deployment": {"io": {"io_binding": binding}}, "models": []} conf = selection.build_run_conf(pipeline, {}, "pipeline.json", root, root) self.assertEqual(conf, {"pipe_path": "pipeline.json"}) self.assertEqual(pipeline["deployment"]["io"]["io_binding"], binding) - for explicit in ("custom.operator.v3", "", None, 42): + for explicit in ("custom_biz", "", None, 42): with self.subTest(explicit=explicit): io_overrides(pipeline)["io_binding"] = explicit selection.build_run_conf(pipeline, {}, "pipeline.json", root, root) @@ -2174,7 +2175,7 @@ def test_authoring_batch_schema_errors_report_index_and_no_candidate(self): def test_authoring_add_node_explicit_auto_id_and_collision(self): req = { "schema_version": 1, - "pipeline": {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []}, + "pipeline": {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []}, "operation": { "kind": "add_node", "node_type": "TextRuleMatchNode", @@ -2232,7 +2233,7 @@ def test_authoring_add_node_explicit_auto_id_and_collision(self): def test_authoring_remove_node_detaches_inputs_and_dependencies(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2269,7 +2270,7 @@ def test_authoring_remove_node_detaches_inputs_and_dependencies(self): def test_authoring_rename_node_syncs_dependencies_and_preserves_keys(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2320,7 +2321,7 @@ def test_authoring_rename_node_syncs_dependencies_and_preserves_keys(self): def test_authoring_connect_and_cycle_and_redundant_dependencies(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2391,7 +2392,7 @@ def test_authoring_connect_and_cycle_and_redundant_dependencies(self): def test_authoring_connect_and_disconnect_biz_egress(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2447,7 +2448,7 @@ def test_authoring_connect_and_disconnect_biz_egress(self): def test_authoring_disconnect_preserves_extra_order_and_removes_input(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2497,7 +2498,7 @@ def test_authoring_disconnect_preserves_extra_order_and_removes_input(self): self.assertEqual(res_rm_dep["pipeline"]["pipeline"][1]["depends_on"], []) def test_authoring_batch_operations_and_atomic_rollback(self): - pipe = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []} + pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req_valid = { "schema_version": 1, "pipeline": pipe, @@ -2543,7 +2544,7 @@ def test_authoring_batch_operations_and_atomic_rollback(self): self.assertFalse(res_oversize["ok"]) def test_authoring_regression_case_2_key_allocation_no_collision(self): - pipe = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []} + pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req = { "schema_version": 1, "pipeline": pipe, @@ -2566,7 +2567,7 @@ def test_authoring_regression_case_2_key_allocation_no_collision(self): def test_studio_authoring_preview_endpoint(self): - pipe = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []} + pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} res = self.service.preview_authoring( pipe, operation={"kind": "add_node", "node_type": "TextRuleMatchNode", "id": "n1"}, @@ -2751,7 +2752,7 @@ def test_studio_deployment_associate_and_model_path_update(self): self.assertEqual(updated_conf, {"pipe_path": pipe_path.name}) def test_authoring_oversized_payload_rejection_4mib(self): - pipe = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []} + pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} large_comment = "x" * (4 * 1024 * 1024 + 100) req = { "schema_version": 1, @@ -2765,7 +2766,7 @@ def test_authoring_oversized_payload_rejection_4mib(self): self.assertIn("REQUEST_TOO_LARGE", res["diagnostics"][0]["message"]) def test_authoring_reserved_node_ids_rejected(self): - pipe = {"deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, "models": [], "pipeline": []} + pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req_add_ingress = { "schema_version": 1, "pipeline": pipe, @@ -2777,7 +2778,7 @@ def test_authoring_reserved_node_ids_rejected(self): self.assertIn("RESERVED_NODE_ID", res1["diagnostics"][0]["message"]) pipe2 = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ {"id": "node_a", "node_type": "TextRuleMatchNode", "depends_on": [], "inputs": {}, "outputs": {}, "config": {}} @@ -2796,7 +2797,7 @@ def test_authoring_reserved_node_ids_rejected(self): def test_authoring_disconnect_requires_explicit_binding(self): # 同名输出加显式顺序不得产生输入绑定。 pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2831,7 +2832,7 @@ def test_authoring_disconnect_requires_explicit_binding(self): # 断开入口连接 pipe_ing = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2859,7 +2860,7 @@ def test_authoring_disconnect_requires_explicit_binding(self): def test_authoring_disconnect_egress_syncs_explicit_consumers(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2900,7 +2901,7 @@ def test_authoring_disconnect_egress_syncs_explicit_consumers(self): def test_authoring_remove_node_handles_consumers_and_egress(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ { @@ -2932,7 +2933,7 @@ def test_authoring_remove_node_handles_consumers_and_egress(self): def test_authoring_add_dependency_avoids_redundant_indirect_ancestor(self): pipe = { - "deployment": {"io": {"io_binding": "keyword_match.operator.v1"}}, + "deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": [ {"id": "a", "node_type": "TextRuleMatchNode", "depends_on": [], "inputs": {}, "outputs": {}, "config": {}}, @@ -3089,7 +3090,7 @@ def test_complete_deployment_envelope_is_available(self): io = deployment["properties"]["io"] self.assertEqual(io["required"], ["io_binding"]) self.assertCountEqual(io["properties"]["io_binding"]["enum"], - [b["binding_id"] for b in artifacts["catalog"]["io_bindings"]]) + [b["biz_name"] for b in artifacts["catalog"]["io_bindings"]]) allocation = io["properties"]["out_mem"]["additionalProperties"] self.assertEqual(set(allocation["properties"]), {"allocator", "params", "meta_num", diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 5dc14422..7cc42fd3 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -137,11 +137,11 @@ struct AudioOutputFixture { // ========================================================================= TEST_F(AdapterPurityTest, DocQaAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "doc_query.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("doc_query.plain"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -192,11 +192,11 @@ TEST_F(AdapterPurityTest, DocQaAdapterPurity) { } TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("keyword.result"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -223,11 +223,11 @@ TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { } TEST_F(AdapterPurityTest, EntityExtractAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.operator.v1"); + "document.structured"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -253,11 +253,11 @@ TEST_F(AdapterPurityTest, EntityExtractAdapterPurity) { } TEST_F(AdapterPurityTest, DialogueAuditAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audit.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audit.plain"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audit_result.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -294,11 +294,11 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapterPurity) { } TEST_F(AdapterPurityTest, OcrInvoiceQaAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "image_query.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("image_query.plain"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "invoice_result.plain.operator.v1"); + "invoice_result.plain"); ASSERT_NE(out_conv, nullptr); std::string path_str = "/path/invoice.jpg"; @@ -357,11 +357,11 @@ TEST_F(AdapterPurityTest, OcrInvoiceQaAdapterPurity) { } TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audio.pcm.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audio.pcm"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audio_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audio_result.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -393,11 +393,11 @@ TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { } TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "rerank.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("rerank.plain"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "rerank_result.plain.operator.v1"); + "rerank_result.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -437,11 +437,11 @@ TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { } TEST_F(AdapterPurityTest, TranslateAdapterPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -472,8 +472,8 @@ TEST_F(AdapterPurityTest, TranslateAdapterPurity) { // ========================================================================= TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(out_conv, nullptr); // 情形 1:缺少 llm_answers @@ -548,8 +548,8 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { TEST_F(AdapterPurityTest, DialogueAuditAdapter_FailClosedWhenMissingStructuredFields) { - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audit_result.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(out_conv); @@ -574,8 +574,8 @@ TEST_F(AdapterPurityTest, } TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audit_result.plain"); ASSERT_NE(out_conv, nullptr); for (const auto parse_status : @@ -612,8 +612,8 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { } TEST_F(AdapterPurityTest, OneToOneResultsRejectDuplicateAndOutOfRangeIds) { - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("keyword.result"); ASSERT_NE(out_conv, nullptr); for (const auto& ids : @@ -632,8 +632,8 @@ TEST_F(AdapterPurityTest, OneToOneResultsRejectDuplicateAndOutOfRangeIds) { } TEST_F(AdapterPurityTest, DialogueAuditAdapter_RejectsOversizedChannelName) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audit.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audit.plain"); ASSERT_NE(in_conv, nullptr); const std::string valid_channel(256, 'c'); @@ -672,8 +672,8 @@ TEST_F(AdapterPurityTest, RuleMatchBatch{{0, 0, RuleMatchItem(1, "QA", "", 0.9f)}}); ctx.Publish("doc_chunk_counts", Int32Batch{{0, 0, 1}}); - const auto* op_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* op_conv = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(op_conv, nullptr); OutputEncodeOptions options; @@ -731,8 +731,8 @@ TEST_F(AdapterPurityTest, } TEST_F(AdapterPurityTest, DocAnswerExactCapacityAndOneByteOverflow) { - const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(converter, nullptr); const std::vector request_ids{42}; OutputEncodeOptions options; @@ -787,8 +787,8 @@ TEST_F(AdapterPurityTest, DocAnswerExactCapacityAndOneByteOverflow) { } TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(in_conv, nullptr); test::AdapterHarness harness(in_conv); @@ -809,11 +809,11 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { } TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(in_conv, out_conv); @@ -847,8 +847,8 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { } TEST_F(AdapterPurityTest, InputBatchSkeleton_AllSamplesValidatedBeforePublish) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(in_conv, nullptr); test::AdapterHarness harness(in_conv); @@ -870,8 +870,8 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_AllSamplesValidatedBeforePublish) { } TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness(out_conv); @@ -914,23 +914,23 @@ TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { // 并通过测试 binding 证明可复用 TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { const auto* entity_binding = - IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); + IoBindingRegistry::Instance().FindBinding("entity_extract"); ASSERT_NE(entity_binding, nullptr); const auto* keyword_binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match"); ASSERT_NE(keyword_binding, nullptr); - EXPECT_EQ(entity_binding->input_converter_id, "text.plain.operator.v1"); - EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.operator.v1"); + EXPECT_EQ(entity_binding->input_converter_id, "text.plain"); + EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain"); - const auto* entity_conv = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* entity_conv = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(entity_conv, nullptr); EXPECT_EQ(ExternalType(entity_conv->external_slots), "CompanyOperatorEntityInput"); - const auto* keyword_conv = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.operator.v1"); + const auto* keyword_conv = + IoConverterRegistry::Instance().FindInputConverter("keyword.plain"); ASSERT_NE(keyword_conv, nullptr); EXPECT_EQ(ExternalType(keyword_conv->external_slots), "CompanyOperatorKeywordInput"); @@ -963,26 +963,25 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { EXPECT_EQ((*sentences)[0].data, "keyword sentence"); } - // 跨业务复用证明:在测试专用业务的绑定中复用 text.plain.operator.v1 + // 跨业务复用证明:在测试专用业务的绑定中复用 text.plain { auto reuse_biz = *PipelineCatalog::FindBiz("entity_extract"); reuse_biz.biz_name = "test_purity_reuse"; PipelineCatalog::RegisterBizDefinition(reuse_biz); IoBindingDefinition test_reuse_binding; - test_reuse_binding.binding_id = "test_purity_reuse.operator.v1"; test_reuse_binding.biz_name = reuse_biz.biz_name; - test_reuse_binding.input_converter_id = "text.plain.operator.v1"; - test_reuse_binding.output_converter_id = "document.structured.operator.v1"; + test_reuse_binding.input_converter_id = "text.plain"; + test_reuse_binding.output_converter_id = "document.structured"; test_reuse_binding.max_batch_size = 64; if (!IoBindingRegistry::Instance().FindBinding( - test_reuse_binding.binding_id)) { + test_reuse_binding.biz_name)) { ASSERT_TRUE( IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding)); } - const auto* b_test = IoBindingRegistry::Instance().FindBinding( - "test_purity_reuse.operator.v1"); + const auto* b_test = + IoBindingRegistry::Instance().FindBinding("test_purity_reuse"); ASSERT_NE(b_test, nullptr); EXPECT_EQ(b_test->input_converter_id, entity_binding->input_converter_id); } @@ -991,7 +990,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { // 证明 2:输出 Converter 可跨 Pipeline 复用 TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.operator.v1"); + "document.structured"); ASSERT_NE(out_conv, nullptr); // 上下文 A:实体抽取 Pipeline 的输出 @@ -1035,7 +1034,7 @@ TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { TEST_F(AdapterPurityTest, ReuseProof_3_MultipleExternalInputFormatsForSamePipeline) { InputConverterDefinition custom_in_def; - custom_in_def.converter_id = "test.multi_field.operator.v1"; + custom_in_def.converter_id = "test.multi_field"; custom_in_def.schema_id = "multi_field.request"; custom_in_def.external_slots = { @@ -1066,11 +1065,11 @@ TEST_F(AdapterPurityTest, EXPECT_TRUE( IoConverterRegistry::Instance().RegisterInputConverter(custom_in_def)); - // 格式 A:经 text.plain.operator.v1 的 CompanyOperatorEntityInput + // 格式 A:经 text.plain 的 CompanyOperatorEntityInput AlgContext ctx_a; { - const auto* in_a = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* in_a = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(in_a, nullptr); std::string text_str = "AI: Revolution in robotics"; CompanyString cs_text{static_cast(text_str.size()), @@ -1090,11 +1089,11 @@ TEST_F(AdapterPurityTest, ASSERT_EQ(in_a->decode_fn(view, opts, &ctx_a, &st), 0); } - // 格式 B:经 test.multi_field.operator.v1 的 CustomMultiFieldInput + // 格式 B:经 test.multi_field 的 CustomMultiFieldInput AlgContext ctx_b; { - const auto* in_b = IoConverterRegistry::Instance().FindInputConverter( - "test.multi_field.operator.v1"); + const auto* in_b = + IoConverterRegistry::Instance().FindInputConverter("test.multi_field"); ASSERT_NE(in_b, nullptr); CustomMultiFieldInput req_b{777, "AI", "Revolution in robotics"}; ExternalInputBatchView view; @@ -1121,11 +1120,11 @@ TEST_F(AdapterPurityTest, // 证明 4:同一 Pipeline 可独立切换输出格式 TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { - // 输出 binding A:document.structured.operator.v1 -> + // 输出 binding A:document.structured -> // CompanyOperatorEntityOutput { const auto* out_a = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.operator.v1"); + "document.structured"); ASSERT_NE(out_a, nullptr); EntityOutputFixture fix; std::vector outputs = {fix.out}; @@ -1142,11 +1141,11 @@ TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { EXPECT_STREQ(outputs[0].entities_json->data, "[\"item_1\"]"); } - // 输出 binding B:keyword.result.operator.v1 -> + // 输出 binding B:keyword.result -> // CompanyOperatorKeywordOutput { - const auto* out_b = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.operator.v1"); + const auto* out_b = + IoConverterRegistry::Instance().FindOutputConverter("keyword.result"); ASSERT_NE(out_b, nullptr); KeywordOutputFixture fix; std::vector outputs = {fix.out}; @@ -1167,11 +1166,11 @@ TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { // 证明 5:同一载体承载不同 schema TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { - const auto* plain_conv = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* plain_conv = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(plain_conv, nullptr); - const auto* json_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* json_conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(json_conv, nullptr); // Payload 1:纯文本 "Hello plain text" @@ -1189,7 +1188,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { InputDecodeOptions opts; opts.request_ids = &request_ids; - // text.plain.operator.v1 将其作为纯文本接受 + // text.plain 将其作为纯文本接受 { AlgContext ctx; AdapterStatus st; @@ -1200,7 +1199,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { EXPECT_EQ((*s)[0].data, "Hello plain text"); } - // translate.json.operator.v1 因其不是 JSON 而拒绝 + // translate.json 因其不是 JSON 而拒绝 { AlgContext ctx; AdapterStatus st; @@ -1219,7 +1218,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { json_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; json_view.count = 1; - // translate.json.operator.v1 解析成功并提取 "query" + // translate.json 解析成功并提取 "query" { AlgContext ctx; AdapterStatus st; @@ -1247,7 +1246,7 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { nlohmann::json bad_binding_json = { {"deployment", {{"io", - {{"io_binding", "non_existent.binding.v999"}, + {{"io_binding", "non_existent_biz"}, {"out_mem", {{"keyword_out", {{"meta_num", 0}, @@ -1266,9 +1265,7 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { // 3. DeploymentIoConfig schema 校验拒绝非法/旧格式 nlohmann::json invalid_version_json = { {"schema_version", 999}, - {"data", - {{"pipe_path", "test.json"}, - {"io_binding", "keyword_match.operator.v1"}}}}; + {"data", {{"pipe_path", "test.json"}, {"io_binding", "keyword_match"}}}}; DeploymentIoConfig parsed_cfg; EXPECT_FALSE(DeploymentIoConfig::Parse(invalid_version_json, ".", &parsed_cfg, &error)); @@ -1278,7 +1275,7 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { nlohmann::json unknown_out_json = { {"deployment", {{"io", - {{"io_binding", "keyword_match.operator.v1"}, + {{"io_binding", "keyword_match"}, {"out_mem", {{"unknown_slot", nlohmann::json::object()}}}}}}}, {"models", nlohmann::json::array()}, {"pipeline", valid_pipeline}}; @@ -1293,7 +1290,7 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { {"deployment", {{"model_paths", {{"non_existent_model", "dummy_path"}}}, {"io", - {{"io_binding", "keyword_match.operator.v1"}, + {{"io_binding", "keyword_match"}, {"out_mem", {{"keyword_out", {{"meta_num", 0}, diff --git a/tests/unit/adapter/test_complex_converters.cpp b/tests/unit/adapter/test_complex_converters.cpp index cb8404c6..982613ba 100644 --- a/tests/unit/adapter/test_complex_converters.cpp +++ b/tests/unit/adapter/test_complex_converters.cpp @@ -24,11 +24,11 @@ class ComplexConvertersTest : public ::testing::Test {}; // ==================== DocQA ==================== TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "doc_query.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("doc_query.plain"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(out_conv, nullptr); std::string doc_text = "Sample document text"; @@ -105,8 +105,8 @@ TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { TEST_F(ComplexConvertersTest, DocQaOutputPreservesEmbeddedNullAndRejectsSmallPool) { - const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); + const auto* converter = + IoConverterRegistry::Instance().FindOutputConverter("doc_answer.plain"); ASSERT_NE(converter, nullptr); const std::string answer("a\0b", 3); AlgContext ctx; @@ -148,11 +148,11 @@ TEST_F(ComplexConvertersTest, // ==================== CrossRerank ==================== TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "rerank.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("rerank.plain"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "rerank_result.plain.operator.v1"); + "rerank_result.plain"); ASSERT_NE(out_conv, nullptr); std::string query_text = "how to rerank?"; @@ -215,11 +215,11 @@ TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { // ==================== DialogueAudit ==================== TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audit.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audit.plain"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audit_result.plain"); ASSERT_NE(out_conv, nullptr); std::string dialogue = "User text violating rules"; @@ -297,11 +297,11 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { // ==================== AudioAsrIntent ==================== TEST_F(ComplexConvertersTest, AudioAsrIntentOperatorInputAndOutput) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audio.pcm.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audio.pcm"); ASSERT_NE(in_conv, nullptr); - const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audio_result.plain.operator.v1"); + const auto* out_conv = + IoConverterRegistry::Instance().FindOutputConverter("audio_result.plain"); ASSERT_NE(out_conv, nullptr); std::vector pcm = {0.1f, 0.2f, -0.1f}; @@ -371,11 +371,11 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentOperatorInputAndOutput) { // ==================== OcrInvoiceQa ==================== TEST_F(ComplexConvertersTest, OcrInvoiceQaOperatorInputAndOutput) { - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "image_query.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("image_query.plain"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "invoice_result.plain.operator.v1"); + "invoice_result.plain"); ASSERT_NE(out_conv, nullptr); // 准备 Operator 输入:frame 和 string @@ -481,7 +481,7 @@ TEST_F(ComplexConvertersTest, AllEightBusinessesRegistered) { // 审核输出 Converter 的逻辑端口与业务出口同名。 TEST_F(ComplexConvertersTest, ComplianceBindingUsesBizPortNames) { const auto* binding = - IoBindingRegistry::Instance().FindBinding("dialogue_audit.operator.v1"); + IoBindingRegistry::Instance().FindBinding("dialogue_audit"); ASSERT_NE(binding, nullptr); const auto* output = IoConverterRegistry::Instance().FindOutputConverter( binding->output_converter_id); diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index 61de64d4..be8a9c72 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -111,7 +111,7 @@ class IoBindingRegistryTest : public ::testing::Test { // 注册业务契约 BizDefinition biz; - biz.biz_name = "test_biz_v1"; + biz.biz_name = "test_biz"; biz.ingress = {BizPortDefinition("input_sentences", "TextBatch", true)}; biz.egress = {BizPortDefinition("llm_answers", "TextBatch", true)}; PipelineCatalog::RegisterBizDefinition(biz); @@ -124,8 +124,7 @@ class IoBindingRegistryTest : public ::testing::Test { void RegisterTestBizBinding(size_t max_batch_size = 0) { IoBindingDefinition binding; - binding.binding_id = "test_biz.operator.v1"; - binding.biz_name = "test_biz_v1"; + binding.biz_name = "test_biz"; binding.input_converter_id = "test.in.operator"; binding.output_converter_id = "test.out.operator"; @@ -180,8 +179,7 @@ TEST_F(IoBindingRegistryTest, RegisterAndAuditValidBinding) { auto& reg = IoBindingRegistry::Instance(); IoBindingDefinition binding; - binding.binding_id = "test_biz.operator.v1"; - binding.biz_name = "test_biz_v1"; + binding.biz_name = "test_biz"; binding.input_converter_id = "test.in.operator"; binding.output_converter_id = "test.out.operator"; @@ -198,7 +196,6 @@ TEST_F(IoBindingRegistryTest, AuditRejectsUnregisteredConvertersAndBiz) { // 1. 引用不存在的 biz_name IoBindingDefinition bad_biz; - bad_biz.binding_id = "bad_biz.binding"; bad_biz.biz_name = "non_existent_biz"; bad_biz.input_converter_id = "test.in.operator"; @@ -219,8 +216,7 @@ TEST_F(IoBindingRegistryTest, AuditRejectsUnregisteredConvertersAndBiz) { // 2. 引用不存在的转换器 IoBindingDefinition bad_conv; - bad_conv.binding_id = "bad_conv.binding"; - bad_conv.biz_name = "test_biz_v1"; + bad_conv.biz_name = "test_biz"; bad_conv.input_converter_id = "non_existent_input"; bad_conv.output_converter_id = "test.out.operator"; @@ -257,7 +253,7 @@ TEST_F(IoBindingRegistryTest, })); const nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; @@ -277,8 +273,7 @@ TEST_F(IoBindingRegistryTest, BindingWithoutExplicitLimitUsesStandardDefault) { ASSERT_TRUE(IoConverterRegistry::Instance().RegisterInputConverter(input)); ASSERT_TRUE(IoConverterRegistry::Instance().RegisterOutputConverter(output)); IoBindingDefinition binding; - binding.binding_id = "test_biz.operator.v1"; - binding.biz_name = "test_biz_v1"; + binding.biz_name = "test_biz"; binding.input_converter_id = input.converter_id; binding.output_converter_id = output.converter_id; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); @@ -286,7 +281,7 @@ TEST_F(IoBindingRegistryTest, BindingWithoutExplicitLimitUsesStandardDefault) { EXPECT_TRUE(IoBindingRegistry::Instance().Audit(&errors)); EXPECT_TRUE(errors.empty()); const nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", binding.binding_id}}}}}, + {"deployment", {{"io", {{"io_binding", binding.biz_name}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; @@ -327,8 +322,7 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { // 1. 注册合法绑定 IoBindingDefinition valid_binding; - valid_binding.binding_id = "test_biz.operator.v1"; - valid_binding.biz_name = "test_biz_v1"; + valid_binding.biz_name = "test_biz"; valid_binding.input_converter_id = "test.in.operator"; valid_binding.output_converter_id = "test.out.operator"; @@ -340,11 +334,10 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { EXPECT_TRUE(errors.empty()); // 2. 为另一业务注册未被选用的非法绑定:输入端口不在业务契约中 - auto unselected_biz = *PipelineCatalog::FindBiz("test_biz_v1"); + auto unselected_biz = *PipelineCatalog::FindBiz("test_biz"); unselected_biz.biz_name = "unselected_biz"; ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(unselected_biz)); IoBindingDefinition illegal_binding; - illegal_binding.binding_id = "unselected_bad.operator.v1"; illegal_binding.biz_name = unselected_biz.biz_name; illegal_binding.input_converter_id = RegisterUnmatchedInputConverter(); @@ -390,8 +383,7 @@ TEST_F(IoBindingRegistryTest, DeploymentIoConfigValidation) { // 2. 拒绝旧 Schema 1 包装 (schema_version + data) nlohmann::json old_schema1 = { {"schema_version", 1}, - {"data", - {{"pipe_path", "test.json"}, {"io_binding", "test_biz.operator.v1"}}}}; + {"data", {{"pipe_path", "test.json"}, {"io_binding", "test_biz"}}}}; EXPECT_FALSE(DeploymentIoConfig::Parse(old_schema1, tmp_dir, &parsed, &err)); EXPECT_NE(err.find("Unknown field"), std::string::npos); @@ -571,14 +563,14 @@ TEST_F(IoBindingRegistryTest, StrictConfigDirectoryIsolationAndCwdInvariance) { TEST_F(IoBindingRegistryTest, SplitPipelineDocumentAndCoreBoundary) { RegisterTestBizBinding(); nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", nlohmann::json::array()}, {"max_parallel_workers", 2}, {"pipeline", DefaultPipelineNodes()}}; PipelineDocumentSplit split; std::string error; ASSERT_TRUE(SplitPipelineDocument(document, &split, &error)) << error; - EXPECT_EQ(split.deployment.io.io_binding, "test_biz.operator.v1"); + EXPECT_EQ(split.deployment.io.io_binding, "test_biz"); EXPECT_EQ(split.deployment.io.out_mem, nlohmann::json::object()); EXPECT_FALSE(split.neutral_pipeline_json.contains("deployment")); EXPECT_FALSE(split.neutral_pipeline_json.contains("biz_name")); @@ -590,7 +582,7 @@ TEST_F(IoBindingRegistryTest, SplitPipelineDocumentAndCoreBoundary) { DeploymentDiagnostic diagnostic; ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) << diagnostic.message; - EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz_v1"); + EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz"); EXPECT_FALSE(document.contains("biz_name")); ParsedPipelineConfig parsed; PipelineDiagnostic core_diagnostic; @@ -599,7 +591,7 @@ TEST_F(IoBindingRegistryTest, SplitPipelineDocumentAndCoreBoundary) { EXPECT_EQ(core_diagnostic.path, "/deployment"); EXPECT_TRUE(ParsePipelineConfig(prepared.neutral_pipeline_json, &parsed, &core_diagnostic)); - EXPECT_EQ(parsed.biz_name, "test_biz_v1"); + EXPECT_EQ(parsed.biz_name, "test_biz"); EXPECT_EQ(parsed.max_parallel_workers, 2); for (const char* field : {"unknown_key", "model_path"}) { @@ -626,7 +618,7 @@ TEST_F(IoBindingRegistryTest, UnknownExternalRootFieldsAreRejectedWithEscapedPointers) { RegisterTestBizBinding(); const nlohmann::json valid_document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", DefaultPipelineNodes()}}; struct UnknownRootField { const char* name; @@ -641,7 +633,7 @@ TEST_F(IoBindingRegistryTest, for (const auto& field : fields) { SCOPED_TRACE(field.name); auto document = valid_document; - document[field.name] = "test_biz_v1"; + document[field.name] = "test_biz"; const std::string expected_message = std::string("Unknown field at ") + field.pointer; PipelineDocumentSplit split; @@ -660,7 +652,7 @@ TEST_F(IoBindingRegistryTest, EXPECT_EQ(diagnostic.code, "DEPLOYMENT_ERROR"); EXPECT_EQ(diagnostic.path, field.pointer); EXPECT_NE(error.find(expected_message), std::string::npos); - EXPECT_EQ(document[field.name], "test_biz_v1"); + EXPECT_EQ(document[field.name], "test_biz"); } } @@ -669,7 +661,7 @@ TEST_F(IoBindingRegistryTest, EscapedJsonPointerInOutputSlots) { nlohmann::json slot_doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"slot~0/bad", nlohmann::json::object()}}}}}}}, {"models", nlohmann::json::array()}, {"pipeline", DefaultPipelineNodes()}}; @@ -688,7 +680,7 @@ TEST_F(IoBindingRegistryTest, EscapedJsonPointerInOutputSlots) { TEST_F(IoBindingRegistryTest, ModelPathResolutionFailurePointsToModelEntry) { RegisterTestBizBinding(); nlohmann::json doc = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", {{{"model_id", "mid~test/path"}, {"model_type", "test_biz_embedding"}, @@ -713,7 +705,7 @@ TEST_F(IoBindingRegistryTest, PrepareDeploymentSuccessAndBoundaryExtraction_T01) { RegisterTestBizBinding(); nlohmann::json doc = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", {{{"model_id", "mid_1"}, {"model_type", "test_biz_embedding"}, @@ -737,9 +729,9 @@ TEST_F(IoBindingRegistryTest, ASSERT_TRUE(PrepareDeploymentDocument(doc, options, &prepared, &diag)) << diag.message; EXPECT_FALSE(prepared.neutral_pipeline_json.contains("deployment")); - EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz_v1"); + EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz"); EXPECT_EQ(prepared.neutral_pipeline_json["models"], doc["models"]); - EXPECT_EQ(prepared.binding.binding_id, "test_biz.operator.v1"); + EXPECT_EQ(prepared.binding.biz_name, "test_biz"); ASSERT_NE(prepared.input_converter, nullptr); ASSERT_NE(prepared.output_converter, nullptr); EXPECT_EQ(prepared.output_specs.count("entity_out"), 1u); @@ -761,7 +753,7 @@ TEST_F(IoBindingRegistryTest, TEST_F(IoBindingRegistryTest, ExternalBindingIsRequiredWhileCoreUsesNeutralBusiness) { RegisterTestBizBinding(); - const nlohmann::json neutral = {{"biz_name", "test_biz_v1"}, + const nlohmann::json neutral = {{"biz_name", "test_biz"}, {"pipeline", DefaultPipelineNodes()}}; ParsedPipelineConfig parsed; PipelineDiagnostic core_diagnostic; @@ -781,14 +773,14 @@ TEST_F(IoBindingRegistryTest, EXPECT_EQ(diagnostic.path, variant == 0 ? "/deployment" : variant == 1 ? "/deployment/io" : "/deployment/io/io_binding"); - EXPECT_TRUE(prepared.binding.binding_id.empty()); + EXPECT_TRUE(prepared.binding.biz_name.empty()); } } TEST_F(IoBindingRegistryTest, ModelPathMissingEmptyOrWrongTypeRejected_T03) { RegisterTestBizBinding(); const nlohmann::json base_doc = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", {{{"model_id", "mid_1"}, {"model_type", "test_biz_embedding"}, @@ -833,7 +825,7 @@ TEST_F(IoBindingRegistryTest, ModelStructureInvalidRejected_T04) { nlohmann::json doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", nlohmann::json::object()}}}}}}}, {"models", {{"mid_1", "not_an_array"}}}, {"pipeline", DefaultPipelineNodes()}}; @@ -847,7 +839,7 @@ TEST_F(IoBindingRegistryTest, ModelStructureInvalidRejected_T04) { nlohmann::json doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", nlohmann::json::object()}}}}}}}, {"models", {{{"model_id", "mid_1"}, @@ -864,7 +856,7 @@ TEST_F(IoBindingRegistryTest, ModelStructureInvalidRejected_T04) { nlohmann::json doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", nlohmann::json::object()}}}}}}}, {"models", {{{"model_id", "mid_1"}, @@ -889,7 +881,7 @@ TEST_F(IoBindingRegistryTest, ModelPathNonexistentOnDiskIsAllowed_T05) { const auto missing_path = temp_dir / "missing_dir/model.bin"; ASSERT_FALSE(fs::exists(missing_path)); const nlohmann::json doc = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", {{{"model_id", "mid_1"}, {"model_type", "test_biz_embedding"}, @@ -916,7 +908,7 @@ TEST_F(IoBindingRegistryTest, ModelPathNonexistentOnDiskIsAllowed_T05) { TEST_F(IoBindingRegistryTest, RemovedModelPathsIsAlwaysUnknownField_T06) { RegisterTestBizBinding(); const nlohmann::json base_doc = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", {{{"model_id", "mid_1"}, {"model_type", "test_biz_embedding"}, @@ -950,7 +942,7 @@ TEST_F(IoBindingRegistryTest, RemovedModelPathsIsAlwaysUnknownField_T06) { EXPECT_NE(diag.message.find("Unknown field at /deployment/model_paths"), std::string::npos); EXPECT_TRUE(prepared.neutral_pipeline_json.is_null()); - EXPECT_TRUE(prepared.binding.binding_id.empty()); + EXPECT_TRUE(prepared.binding.biz_name.empty()); EXPECT_EQ(doc, original); } } @@ -959,7 +951,7 @@ TEST_F(IoBindingRegistryTest, OutputMemoryOverridesRemainOptional) { RegisterTestBizBinding(); for (int variant = 0; variant < 3; ++variant) { nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", DefaultPipelineNodes()}}; if (variant == 1) document["deployment"]["io"]["out_mem"] = nlohmann::json::object(); @@ -971,7 +963,7 @@ TEST_F(IoBindingRegistryTest, OutputMemoryOverridesRemainOptional) { DeploymentDiagnostic diagnostic; ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) << diagnostic.message; - EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz_v1"); + EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz"); ASSERT_EQ(prepared.output_specs.size(), 1U); const auto& spec = prepared.output_specs.at("entity_out"); EXPECT_EQ(spec.type, "entity_out"); @@ -982,28 +974,22 @@ TEST_F(IoBindingRegistryTest, OutputMemoryOverridesRemainOptional) { } } -TEST_F(IoBindingRegistryTest, - ExplicitBindingSelectsRegisteredBusinessWithoutNameInference) { - IoBindingDefinition alternate; - alternate.binding_id = "unrelated_name.for.explicit_selection"; - alternate.biz_name = "test_biz_v1"; - alternate.input_converter_id = "test.in.operator"; - alternate.output_converter_id = "test.out.operator"; - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(alternate)); +TEST_F(IoBindingRegistryTest, ExplicitIoBindingSelectsRegisteredBusiness) { + RegisterTestBizBinding(); nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", alternate.binding_id}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) << diagnostic.message; - EXPECT_EQ(prepared.binding.binding_id, alternate.binding_id); - EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz_v1"); + EXPECT_EQ(prepared.binding.biz_name, "test_biz"); + EXPECT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz"); EXPECT_FALSE(document.contains("biz_name")); document["deployment"]["io"].erase("io_binding"); EXPECT_FALSE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)); EXPECT_EQ(diagnostic.path, "/deployment/io/io_binding"); - document["deployment"]["io"]["io_binding"] = "unknown.explicit.binding"; + document["deployment"]["io"]["io_binding"] = "unknown_biz"; EXPECT_FALSE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)); EXPECT_EQ(diagnostic.code, "UNKNOWN_IO_BINDING"); EXPECT_EQ(diagnostic.path, "/deployment/io/io_binding"); @@ -1025,14 +1011,13 @@ TEST_F(IoBindingRegistryTest, DefaultsAndOverridesKeepOptionalOutputOptIn) { ASSERT_TRUE( IoConverterRegistry::Instance().RegisterOutputConverter(converter)); IoBindingDefinition binding; - binding.binding_id = "multiple.outputs"; - binding.biz_name = "test_biz_v1"; + binding.biz_name = "test_biz"; binding.input_converter_id = "test.in.operator"; binding.output_converter_id = converter.converter_id; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); nlohmann::json document = {{"pipeline", DefaultPipelineNodes()}}; - document["deployment"]["io"]["io_binding"] = binding.binding_id; + document["deployment"]["io"]["io_binding"] = binding.biz_name; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) @@ -1072,25 +1057,25 @@ TEST_F(IoBindingRegistryTest, SecondBindingForSameBizIsRejected) { slot.key_suffix = "entity_out"; ASSERT_TRUE( IoConverterRegistry::Instance().RegisterOutputConverter(converter)); - auto second = - *IoBindingRegistry::Instance().FindBinding("test_biz.operator.v1"); - second.binding_id = "second.binding"; + auto second = *IoBindingRegistry::Instance().FindBinding("test_biz"); second.output_converter_id = converter.converter_id; // 同一业务只有一份外部契约:第二个 binding 不论载体是否兼容都在注册时被拒绝。 EXPECT_FALSE(IoBindingRegistry::Instance().RegisterBinding(second)); - EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(second.binding_id), - nullptr); + EXPECT_EQ(IoBindingRegistry::Instance() + .FindBinding("test_biz") + ->output_converter_id, + "test.out.operator"); std::vector errors; EXPECT_FALSE(IoBindingRegistry::Instance().Audit(&errors)); EXPECT_TRUE(std::any_of(errors.begin(), errors.end(), [](const auto& error) { - return error.find("Duplicate IoBinding for biz_name 'test_biz_v1'") != + return error.find("Duplicate IoBinding for biz_name: test_biz") != std::string::npos; })); IoBindingRegistry::Instance().ResetConflictForTesting(); // 另一业务可以使用自己的载体契约。 - auto separate = *PipelineCatalog::FindBiz("test_biz_v1"); + auto separate = *PipelineCatalog::FindBiz("test_biz"); separate.biz_name = "separate_output_biz"; ASSERT_TRUE(PipelineCatalog::RegisterBizDefinition(separate)); second.biz_name = separate.biz_name; @@ -1099,7 +1084,7 @@ TEST_F(IoBindingRegistryTest, SecondBindingForSameBizIsRejected) { << (errors.empty() ? "" : errors.front()); nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", second.binding_id}}}}}, + {"deployment", {{"io", {{"io_binding", second.biz_name}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; DeploymentDiagnostic diagnostic; @@ -1127,7 +1112,7 @@ TEST_F(IoBindingRegistryTest, EffectiveBatchLimitIncludesBindingBound) { auto output = *IoConverterRegistry::Instance().FindOutputConverter("test.out.operator"); const nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", DefaultPipelineNodes()}}; for (const auto& limits : cases) { SCOPED_TRACE(::testing::Message() @@ -1155,7 +1140,7 @@ TEST_F(IoBindingRegistryTest, RemovedAllocationFieldIsRejected) { const nlohmann::json document = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"output_allocations", nlohmann::json::object()}}}}}, {"pipeline", DefaultPipelineNodes()}}; PreparedDeployment prepared; @@ -1172,7 +1157,7 @@ TEST_F(IoBindingRegistryTest, fs::create_directories(directory); const std::string root = directory.string(); nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"pipeline", {{{"id", "copy"}, {"node_type", "TextTemplateNode"}, @@ -1190,16 +1175,16 @@ TEST_F(IoBindingRegistryTest, root.c_str(), "pipeline.conf", &biz, error, sizeof(error)), 0) << error; - EXPECT_EQ(biz, "test_biz_v1"); + EXPECT_EQ(biz, "test_biz"); EXPECT_EQ(dummy_decode_calls, decoded_before); EXPECT_EQ(dummy_encode_calls, encoded_before); - document["deployment"]["io"]["io_binding"] = "unknown.binding"; + document["deployment"]["io"]["io_binding"] = "unknown_biz"; std::ofstream(directory / "pipeline.json") << document; EXPECT_EQ(operator_api::ResolveOperatorConfigBiz( root.c_str(), "pipeline.conf", &biz, error, sizeof(error)), -2); EXPECT_TRUE(biz.empty()); - EXPECT_NE(std::string(error).find("unknown.binding"), std::string::npos); + EXPECT_NE(std::string(error).find("unknown_biz"), std::string::npos); EXPECT_EQ(operator_api::ResolveOperatorConfigBiz( root.c_str(), "pipeline.conf", nullptr, error, sizeof(error)), -2); @@ -1212,7 +1197,7 @@ TEST_F(IoBindingRegistryTest, DeploymentIoUnknownBindingOrMismatch_T07) { nlohmann::json base_doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -1252,7 +1237,7 @@ TEST_F(IoBindingRegistryTest, DeploymentIoSlotValidation_T08) { nlohmann::json base_doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -1302,7 +1287,7 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { nlohmann::json valid_doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -1326,7 +1311,7 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { // 1. 首次准备成功 ASSERT_TRUE(PrepareDeploymentDocument(valid_doc, options, &prepared, &diag)); - EXPECT_FALSE(prepared.binding.binding_id.empty()); + EXPECT_FALSE(prepared.binding.biz_name.empty()); EXPECT_NE(prepared.input_converter, nullptr); EXPECT_NE(prepared.output_converter, nullptr); EXPECT_FALSE(prepared.neutral_pipeline_json.is_null()); @@ -1334,14 +1319,14 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { // 2. 在失败的文档上复用同一个 prepared 实例 nlohmann::json invalid_doc = valid_doc; - invalid_doc["deployment"]["io"]["io_binding"] = "unknown.binding"; + invalid_doc["deployment"]["io"]["io_binding"] = "unknown_biz"; const nlohmann::json invalid_doc_copy = invalid_doc; EXPECT_FALSE( PrepareDeploymentDocument(invalid_doc, options, &prepared, &diag)); // 验证 prepared 的所有字段都已完全重置 - EXPECT_TRUE(prepared.binding.binding_id.empty()); + EXPECT_TRUE(prepared.binding.biz_name.empty()); EXPECT_EQ(prepared.input_converter, nullptr); EXPECT_EQ(prepared.output_converter, nullptr); EXPECT_EQ(prepared.effective_max_batch_size, 0u); @@ -1370,7 +1355,7 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { PrepareDeploymentDocument(invalid_doc, options, &prepared, &diag)); EXPECT_EQ(diag.code, "INVALID_MODEL_PATH"); EXPECT_EQ(diag.path, "/models/1/model_path"); - EXPECT_TRUE(prepared.binding.binding_id.empty()); + EXPECT_TRUE(prepared.binding.biz_name.empty()); EXPECT_EQ(prepared.input_converter, nullptr); EXPECT_EQ(prepared.output_converter, nullptr); EXPECT_EQ(prepared.effective_max_batch_size, 0u); @@ -1433,7 +1418,7 @@ TEST_F(IoBindingRegistryTest, ProjectDerivedBusinessDiagnosticsKeepsOnlyExternalFixes) { RegisterTestBizBinding(); const nlohmann::json external_document = { - {"deployment", {{"io", {{"io_binding", "test_biz.operator.v1"}}}}}, + {"deployment", {{"io", {{"io_binding", "test_biz"}}}}}, {"models", nlohmann::json::array()}, {"pipeline", DefaultPipelineNodes()}}; DeploymentPrepareOptions options; @@ -1442,13 +1427,13 @@ TEST_F(IoBindingRegistryTest, DeploymentDiagnostic deployment_diagnostic; ASSERT_TRUE(PrepareDeploymentDocument(external_document, options, &prepared, &deployment_diagnostic)); - ASSERT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz_v1"); + ASSERT_EQ(prepared.neutral_pipeline_json["biz_name"], "test_biz"); ASSERT_FALSE(external_document.contains("biz_name")); ValidationFix derived_business_fix; derived_business_fix.id = "edit_derived_business"; derived_business_fix.patch = nlohmann::json::array( - {{{"op", "add"}, {"path", "/biz_name"}, {"value", "test_biz_v1"}}}); + {{{"op", "add"}, {"path", "/biz_name"}, {"value", "test_biz"}}}); ValidationFix node_fix; node_fix.id = "correct_rule_keyword"; @@ -1506,7 +1491,7 @@ TEST_F(IoBindingRegistryTest, nlohmann::json t03_doc = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -1544,7 +1529,7 @@ TEST_F(IoBindingRegistryTest, // T07:经 IoBindingResolver 的未知 binding nlohmann::json t07_doc = t03_doc; t07_doc["models"][0]["model_path"] = "models/original.bin"; - t07_doc["deployment"]["io"]["io_binding"] = "nonexistent.binding"; + t07_doc["deployment"]["io"]["io_binding"] = "nonexistent_biz"; rc = IoBindingResolver::ResolveFromPipelineJson(t07_doc, "./models", &plan, &err, &diag); EXPECT_EQ(rc, -2); @@ -1599,7 +1584,7 @@ TEST_F(IoBindingRegistryTest, nlohmann::json base_pipeline = { {"deployment", {{"io", - {{"io_binding", "test_biz.operator.v1"}, + {{"io_binding", "test_biz"}, {"out_mem", {{"entity_out", {{"meta_num", 0}, @@ -1660,7 +1645,7 @@ TEST_F(IoBindingRegistryTest, // 4. 经文件的 T07:未知 io_binding { nlohmann::json t07_pipe = base_pipeline; - t07_pipe["deployment"]["io"]["io_binding"] = "unregistered.binding"; + t07_pipe["deployment"]["io"]["io_binding"] = "unregistered_biz"; write_file(pipe_path, t07_pipe); rc = IoBindingResolver::ResolveFromFile(conf_path.string(), "", &plan, &err, diff --git a/tests/unit/adapter/test_io_converters.cpp b/tests/unit/adapter/test_io_converters.cpp index 44f49075..bf8a4920 100644 --- a/tests/unit/adapter/test_io_converters.cpp +++ b/tests/unit/adapter/test_io_converters.cpp @@ -75,7 +75,7 @@ TEST(IoConverterTest, RegisterAndFindInputConverter) { auto& reg = IoConverterRegistry::Instance(); InputConverterDefinition def; - def.converter_id = "test.input.operator.v1"; + def.converter_id = "test.input"; def.schema_id = "test_input"; def.external_slots = {ExternalSlotDefinition( @@ -86,9 +86,9 @@ TEST(IoConverterTest, RegisterAndFindInputConverter) { EXPECT_TRUE(reg.RegisterInputConverter(def)); - const auto* found = reg.FindInputConverter("test.input.operator.v1"); + const auto* found = reg.FindInputConverter("test.input"); ASSERT_NE(found, nullptr); - EXPECT_EQ(found->converter_id, "test.input.operator.v1"); + EXPECT_EQ(found->converter_id, "test.input"); EXPECT_EQ(found->logical_ports.size(), 1U); @@ -101,7 +101,7 @@ TEST(IoConverterTest, RegisterAndFindOutputConverter) { auto& reg = IoConverterRegistry::Instance(); OutputConverterDefinition def; - def.converter_id = "test.output.operator.v1"; + def.converter_id = "test.output"; def.schema_id = "test_output"; def.external_slots = {ExternalSlotDefinition( @@ -112,9 +112,9 @@ TEST(IoConverterTest, RegisterAndFindOutputConverter) { EXPECT_TRUE(reg.RegisterOutputConverter(def)); - const auto* found = reg.FindOutputConverter("test.output.operator.v1"); + const auto* found = reg.FindOutputConverter("test.output"); ASSERT_NE(found, nullptr); - EXPECT_EQ(found->converter_id, "test.output.operator.v1"); + EXPECT_EQ(found->converter_id, "test.output"); } TEST(IoConverterTest, ExternalTypeJoinsSlotTypesInOrder) { diff --git a/tests/unit/adapter/test_text_converters.cpp b/tests/unit/adapter/test_text_converters.cpp index e8321a87..c152f8a3 100644 --- a/tests/unit/adapter/test_text_converters.cpp +++ b/tests/unit/adapter/test_text_converters.cpp @@ -20,8 +20,8 @@ namespace llm_edgeflow { class TextConvertersTest : public ::testing::Test {}; TEST_F(TextConvertersTest, TextPlainOperatorInputDecodeSuccess) { - const auto* conv = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.operator.v1"); + const auto* conv = + IoConverterRegistry::Instance().FindInputConverter("text.plain"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->decode_fn, nullptr); @@ -62,8 +62,8 @@ TEST_F(TextConvertersTest, TextPlainOperatorInputDecodeSuccess) { } TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { - const auto* conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.operator.v1"); + const auto* conv = + IoConverterRegistry::Instance().FindInputConverter("translate.json"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->decode_fn, nullptr); @@ -113,8 +113,8 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { } TEST_F(TextConvertersTest, TranslationJsonOutputEncodeOperator) { - const auto* conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* conv = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->encode_fn, nullptr); @@ -155,24 +155,24 @@ TEST_F(TextConvertersTest, TranslationJsonOutputEncodeOperator) { TEST_F(TextConvertersTest, ProductionBindingsUseDeclaredHostTypes) { const auto* entity_binding = - IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); + IoBindingRegistry::Instance().FindBinding("entity_extract"); ASSERT_NE(entity_binding, nullptr); const auto* keyword_binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match"); ASSERT_NE(keyword_binding, nullptr); - // 实体抽取经 text.plain.operator.v1 使用 CompanyOperatorEntityInput - EXPECT_EQ(entity_binding->input_converter_id, "text.plain.operator.v1"); + // 实体抽取经 text.plain 使用 CompanyOperatorEntityInput + EXPECT_EQ(entity_binding->input_converter_id, "text.plain"); const auto* entity_conv = IoConverterRegistry::Instance().FindInputConverter( entity_binding->input_converter_id); ASSERT_NE(entity_conv, nullptr); EXPECT_EQ(ExternalType(entity_conv->external_slots), "CompanyOperatorEntityInput"); - // 关键词匹配经 keyword.plain.operator.v1 使用 + // 关键词匹配经 keyword.plain 使用 // CompanyOperatorKeywordInput - EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.operator.v1"); + EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain"); const auto* keyword_conv = IoConverterRegistry::Instance().FindInputConverter( keyword_binding->input_converter_id); ASSERT_NE(keyword_conv, nullptr); @@ -180,9 +180,8 @@ TEST_F(TextConvertersTest, ProductionBindingsUseDeclaredHostTypes) { "CompanyOperatorKeywordInput"); // 输出 Converter 互不相同 - EXPECT_EQ(entity_binding->output_converter_id, - "document.structured.operator.v1"); - EXPECT_EQ(keyword_binding->output_converter_id, "keyword.result.operator.v1"); + EXPECT_EQ(entity_binding->output_converter_id, "document.structured"); + EXPECT_EQ(keyword_binding->output_converter_id, "keyword.result"); } TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { @@ -191,23 +190,19 @@ TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { reuse_biz.biz_name = "test_text_reuse"; PipelineCatalog::RegisterBizDefinition(reuse_biz); IoBindingDefinition test_reuse_binding; - test_reuse_binding.binding_id = "test_text_reuse.operator.v1"; test_reuse_binding.biz_name = reuse_biz.biz_name; - test_reuse_binding.input_converter_id = "text.plain.operator.v1"; - test_reuse_binding.output_converter_id = "document.structured.operator.v1"; + test_reuse_binding.input_converter_id = "text.plain"; + test_reuse_binding.output_converter_id = "document.structured"; test_reuse_binding.max_batch_size = 64; - if (!IoBindingRegistry::Instance().FindBinding( - test_reuse_binding.binding_id)) { + if (!IoBindingRegistry::Instance().FindBinding(test_reuse_binding.biz_name)) { ASSERT_TRUE( IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding)); } - const auto* b1 = - IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); - const auto* b2 = - IoBindingRegistry::Instance().FindBinding("test_text_reuse.operator.v1"); + const auto* b1 = IoBindingRegistry::Instance().FindBinding("entity_extract"); + const auto* b2 = IoBindingRegistry::Instance().FindBinding("test_text_reuse"); ASSERT_NE(b1, nullptr); ASSERT_NE(b2, nullptr); EXPECT_EQ(b1->input_converter_id, b2->input_converter_id); @@ -239,28 +234,28 @@ TEST_F(TextConvertersTest, SameCarrierDifferentPayloadBindingIsRejected) { : std::string(); }; const std::string request = R"({"query":"hello"})"; - ASSERT_EQ(decode("translate.json.operator.v1", request), "hello"); - ASSERT_EQ(decode("text.plain.operator.v1", request), request); + ASSERT_EQ(decode("translate.json", request), "hello"); + ASSERT_EQ(decode("text.plain", request), request); // 同一业务只登记一个 binding:translate 不能再挂另一协议的 Converter。 auto& bindings = IoBindingRegistry::Instance(); - auto alternate = *bindings.FindBinding("translate.operator.v1"); - alternate.binding_id = "translate.plain_text_input"; - alternate.input_converter_id = "text.plain.operator.v1"; + auto alternate = *bindings.FindBinding("translate"); + alternate.input_converter_id = "text.plain"; EXPECT_FALSE(bindings.RegisterBinding(alternate)); - EXPECT_EQ(bindings.FindBinding(alternate.binding_id), nullptr); + EXPECT_EQ(bindings.FindBinding("translate")->input_converter_id, + "translate.json"); std::vector errors; EXPECT_FALSE(bindings.Audit(&errors)); EXPECT_TRUE(std::any_of(errors.begin(), errors.end(), [](const auto& error) { - return error.find("Duplicate IoBinding for biz_name 'translate'") != + return error.find("Duplicate IoBinding for biz_name: translate") != std::string::npos; })); bindings.ResetConflictForTesting(); } TEST_F(TextConvertersTest, MissingResultsDifferFromOutputCapacityFailures) { - const auto* conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.operator.v1"); + const auto* conv = + IoConverterRegistry::Instance().FindOutputConverter("translate.json"); ASSERT_NE(conv, nullptr); OutputEncodeOptions options; options.converter_id = conv->converter_id; diff --git a/tests/unit/operator/test_operator_value_registry.cpp b/tests/unit/operator/test_operator_value_registry.cpp index 31c6f141..e3b83db9 100644 --- a/tests/unit/operator/test_operator_value_registry.cpp +++ b/tests/unit/operator/test_operator_value_registry.cpp @@ -1125,8 +1125,8 @@ TEST(OperatorValueRegistryTest, TEST(OperatorValueRegistryTest, OperatorAgreesOnChannelNameBoundaries) { const auto* binding = OperatorValueTypeRegistry::Instance().GetBindingBySuffix("audit_in"); - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audit.plain.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audit.plain"); ASSERT_NE(binding, nullptr); ASSERT_NE(in_conv, nullptr); std::string query = "hello"; @@ -1157,8 +1157,8 @@ TEST(OperatorValueRegistryTest, OperatorAgreesOnChannelNameBoundaries) { TEST(OperatorValueRegistryTest, OperatorAgreesOnPcmBoundaries) { const auto* binding = OperatorValueTypeRegistry::Instance().GetBindingBySuffix("audio_in"); - const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "audio.pcm.operator.v1"); + const auto* in_conv = + IoConverterRegistry::Instance().FindInputConverter("audio.pcm"); ASSERT_NE(binding, nullptr); ASSERT_NE(in_conv, nullptr); std::vector samples(biz_input::kMaxAudioPcmSamples, 0); diff --git a/tools/pipeline_studio/README.md b/tools/pipeline_studio/README.md index fa36d4b3..95fb7409 100644 --- a/tools/pipeline_studio/README.md +++ b/tools/pipeline_studio/README.md @@ -107,12 +107,12 @@ Web 服务。需要经过校验的拓扑顺序和波前层时,应使用 从无需模型的关键词方案开始,先查询实际契约: ```bash -./build/alg_pipeline_tool catalog --io-binding keyword_match.operator.v1 +./build/alg_pipeline_tool catalog --io-binding keyword_match ./build/alg_pipeline_tool describe-node TextRuleMatchNode ./tools/pipeline_studio/server.py --web ``` -1. 点击顶部“新建”,选择 I/O 契约 `keyword_match.operator.v1`, +1. 点击顶部“新建”,选择 I/O 契约 `keyword_match`, 从 `keyword_match_rules` Profile 克隆并点击“新建方案”。 2. 在画布检查“业务输入”的 `input_sentences` → 节点 `text`,以及节点 `matches` → “业务输出”的 `rule_matches`。可选中连线后点击“删除连线”,再从输出端口拖到 @@ -140,7 +140,7 @@ Catalog v4 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` ## 自动化 CLI ```bash -./build/alg_pipeline_tool catalog --io-binding doc_qa.operator.v1 +./build/alg_pipeline_tool catalog --io-binding doc_qa ./build/alg_pipeline_tool describe-node TextEmbeddingNode ./build/alg_pipeline_tool validate configs/pipeline_doc_qa_default.json ./build/alg_pipeline_tool plan configs/pipeline_doc_qa_default.json @@ -176,7 +176,7 @@ Catalog v4 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` ### 校验工具选择 CLI 克隆默认返回包含 `pipeline` 的版本化响应。需要直接保存 Pipeline JSON 时使用 -`init --io-binding --profile --raw`,确认命令成功后再对保存文件执行 +`init --io-binding --profile --raw`,确认命令成功后再对保存文件执行 `validate`。`--empty --raw` 生成待填写草稿;`--empty` 与 `--profile` 不能同时指定。 正式配置使用目标构建的 `alg_pipeline_tool`;有意使用测试 Model/Backend 的 Smoke diff --git a/tools/pipeline_studio/web/app.js b/tools/pipeline_studio/web/app.js index 594576be..6dc11192 100644 --- a/tools/pipeline_studio/web/app.js +++ b/tools/pipeline_studio/web/app.js @@ -258,12 +258,11 @@ function renderBizContract() { ]); if (!state.catalogReady) return; const bindings = (state.catalog.io_bindings || []).filter(binding => - binding.binding_id === bindingId); + binding.biz_name === bindingId); for (const binding of bindings) { const heading = document.createElement("h3"); heading.textContent = "接入绑定"; container.append(heading); - fields(container, [["Binding ID", binding.binding_id]]); for (const [direction, label] of [["input", "输入"], ["output", "输出"]]) { const converterId = binding[`${direction}_converter_id`]; const converter = state.catalog[`${direction}_converters`]?.find(item => item.converter_id === converterId); @@ -412,7 +411,7 @@ async function refreshLists() { state.profiles = profiles.profiles; state.catalogProfiles = allCatalog.profiles || []; const bindingSelect = $("#bindingSelect"); bindingSelect.replaceChildren(); - for (const item of allCatalog.io_bindings || []) bindingSelect.add(new Option(item.binding_id, item.binding_id)); + for (const item of allCatalog.io_bindings || []) bindingSelect.add(new Option(item.biz_name, item.biz_name)); if (state.pipeline) bindingSelect.value = pipelineBinding(state.pipeline); const schemes = $("#pipelineSelect"); schemes.replaceChildren(new Option("选择方案", "")); for (const item of pipelines.pipelines) schemes.add(new Option(`${item.filename} · ${item.io_binding}`, item.filename)); diff --git a/tools/pipeline_studio/web/workbench.js b/tools/pipeline_studio/web/workbench.js index 43d2d7b7..90b57065 100644 --- a/tools/pipeline_studio/web/workbench.js +++ b/tools/pipeline_studio/web/workbench.js @@ -68,7 +68,7 @@ export function pipelineBinding(pipeline) { export function graphDocument(pipeline, catalog) { const nodes = pipeline?.pipeline || []; - const binding = catalog.io_bindings?.find(binding => binding.binding_id === pipelineBinding(pipeline)); + const binding = catalog.io_bindings?.find(binding => binding.biz_name === pipelineBinding(pipeline)); const biz = catalog.bizs?.find(biz => biz.biz_name === binding?.biz_name); const definitions = {}; for (const node of nodes) definitions[node.id] = catalog.nodes?.find(def => def.node_type === node.node_type) || {}; From 2cbbf8320ff6fc098cbcfa889d75e09d08d7e58e Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 17:10:35 +0800 Subject: [PATCH 3/4] refactor(tooling): drop schema_version from developer tool JSON The project is pre-release and has a single format for each developer tool document, so the version fields and their checks only added ceremony: every format change required bumping a number and updating the readers that enforced it. Removed from alg_pipeline_tool envelopes, Catalog (was 4), validation reports and remediation, edit requests/responses (a request that still sends schema_version is rejected as an unknown field), Pipeline Studio endpoints and web checks, Demo profiles (was 2) and result files, dev_recipe and verify_selection reports, effects specs, asset manifests and acceptance evidence. Tests that only checked version numbers or rejected old wrappers are removed or now check generic unknown fields. The kiteLLM run config keeps its schema_version: the file is passed unchanged to the pinned third-party runtime. Co-Authored-By: Claude Opus 5.5 --- demo/common/demo_options.cpp | 17 ----- demo/common/demo_options.h | 3 +- demo/common/result_writer.cpp | 2 - demo/profiles.json | 1 - demo/profiles_kite.json | 1 - doc/CHANGELOG.md | 5 ++ doc/VERIFIABLE_SELECTION.md | 2 +- include/core/pipeline_validator.h | 1 - models/asset_manifest.json | 1 - scripts/generate_acceptance_evidence.sh | 1 - src/adapter/io_catalog.cpp | 1 - src/adapter/io_catalog.h | 2 +- .../operator/operator_config_resolver.cpp | 2 +- src/cli/alg_pipeline_tool.cpp | 24 +++---- src/cli/pipeline_authoring.cpp | 13 +--- src/cli/pipeline_authoring.h | 4 +- src/cli/pipeline_document_validation.cpp | 4 +- src/cli/pipeline_remediation.cpp | 9 --- src/core/pipeline_validator.cpp | 6 +- .../test_quality_gate_contract.py | 1 - .../catalog/test_catalog_contract_ssot.cpp | 9 +-- tests/fixtures/asset_manifest_test.json | 1 - tests/fixtures/effects/entity_mock_exact.json | 1 - tests/fixtures/effects/keyword_exact.json | 1 - .../validation/invalid_pipeline_cases.json | 1 - tests/integration/demo/test_demo_runner.cpp | 16 ++--- .../operator/test_operator_api.cpp | 11 +--- .../test_pipeline_catalog_validator.cpp | 3 - tests/tooling/studio_fix_workflow_test.mjs | 35 +---------- tests/tooling/test_dev_recipe.py | 12 ---- tests/tooling/test_pipeline_studio.py | 62 ++++--------------- tests/unit/adapter/test_adapter_purity.cpp | 11 ++-- .../unit/adapter/test_io_binding_registry.cpp | 14 ++--- tools/dev_recipe.py | 12 ++-- tools/pipeline_studio/README.md | 8 +-- tools/pipeline_studio/server.py | 5 +- tools/pipeline_studio/web/app.js | 18 ------ tools/pipeline_studio/web/editor.js | 2 +- tools/verify_selection.py | 8 +-- 39 files changed, 71 insertions(+), 259 deletions(-) diff --git a/demo/common/demo_options.cpp b/demo/common/demo_options.cpp index 3d61268b..5f22b37f 100644 --- a/demo/common/demo_options.cpp +++ b/demo/common/demo_options.cpp @@ -213,23 +213,6 @@ int LoadAndValidateProfilesDocument(const std::string& profiles_path, return 3; } - if (!root.contains("schema_version") || - !root["schema_version"].is_number_integer()) { - if (error_msg) - *error_msg = "Missing or invalid 'schema_version' in profiles file: " + - resolved_path; - return 3; - } - - if (root["schema_version"].get() != 2) { - if (error_msg) { - *error_msg = "Unsupported schema_version: " + - std::to_string(root["schema_version"].get()) + - " (Expected: 2)"; - } - return 3; - } - if (!root.contains("profiles") || !root["profiles"].is_object()) { if (error_msg) *error_msg = diff --git a/demo/common/demo_options.h b/demo/common/demo_options.h index c2db68de..a665aa3b 100644 --- a/demo/common/demo_options.h +++ b/demo/common/demo_options.h @@ -79,8 +79,7 @@ int ParseCommandLine(int argc, char* argv[], DemoOptions* out_options, std::string* error_msg); /** - * @brief 解析并严格校验 profiles.json 文档 (校验 schema_version, profiles - * 结构与所有字段) + * @brief 解析并严格校验 profiles.json 文档 (校验 profiles 结构与所有字段) * @param profiles_path profiles.json 路径 (支持相对或绝对路径,为空默认使用 * demo/profiles.json) * @param out_root 输出解析并校验通过的 JSON 对象 diff --git a/demo/common/result_writer.cpp b/demo/common/result_writer.cpp index 7d8f81f9..ab51c9a2 100644 --- a/demo/common/result_writer.cpp +++ b/demo/common/result_writer.cpp @@ -71,7 +71,6 @@ int ResultWriter::WriteResults(const std::vector& results, run_latencies_sum += sample.latency_ms; nlohmann::json record; - record["schema_version"] = 1; record["profile"] = profile_name; record["biz"] = options_.biz; record["request_id"] = sample.request_id; @@ -149,7 +148,6 @@ int ResultWriter::WriteResults(const std::vector& results, } nlohmann::json summary; - summary["schema_version"] = 1; summary["profile"] = profile_name; summary["biz"] = options_.biz; summary["config_path"] = options_.config_path; diff --git a/demo/profiles.json b/demo/profiles.json index 57dbf980..ee3b62b7 100644 --- a/demo/profiles.json +++ b/demo/profiles.json @@ -1,5 +1,4 @@ { - "schema_version": 2, "profiles": { "entity_extract_mock": { "config": "demo/fixtures/mock/pipeline_entity_extract.conf", diff --git a/demo/profiles_kite.json b/demo/profiles_kite.json index 519754a0..1eb65b8c 100644 --- a/demo/profiles_kite.json +++ b/demo/profiles_kite.json @@ -1,5 +1,4 @@ { - "schema_version": 2, "profiles": { "entity_extract_kite": { "config": "configs/pipeline_entity_extract_kite.conf", diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index cf993a9a..8ca1b0a5 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +开发工具 JSON 不再携带 `schema_version`:`alg_pipeline_tool` 各命令输出、Catalog、校验报告与 +remediation、`edit` 请求与响应、Pipeline Studio 接口、Demo 的 Profile 文件与结果文件、 +`dev_recipe` / `verify_selection` 报告、效果规格、资产清单和验收证据都删除该字段及对应的版本检查; +`edit` 请求携带 `schema_version` 时按未知字段拒绝。kiteLLM 的 run config 属于第三方格式,保持不变。 + IoBinding 改以业务名标识:删除 `IoBindingDefinition::binding_id`,Pipeline 的 `deployment.io.io_binding` 直接填写业务名(如 `keyword_match`),`catalog` / `init` 的 `--io-binding` 同样接受业务名。转换器 ID 去掉 `.operator.v1` 后缀(如 `text.plain`)。Catalog 的 `io_bindings` 与 `validate-io` 不再输出 diff --git a/doc/VERIFIABLE_SELECTION.md b/doc/VERIFIABLE_SELECTION.md index 326a7498..6d695537 100644 --- a/doc/VERIFIABLE_SELECTION.md +++ b/doc/VERIFIABLE_SELECTION.md @@ -65,7 +65,7 @@ python3 tools/verify_selection.py check \ 该 preset 的匹配产物。`--variant` 只校验 Backend 集合,不选择可执行文件;使用 preset 时需显式传入对应目录的 `--tool`,执行效果验收时还需指定同目录的 `--demo`。 -报告使用 `schema_version=2`;报告分别给出 `configuration`、`models`、`build`、`effects` 和 `ready_for_biz`。普通 `check` 的退出码表示配置/资产/构建检查;发布门禁应增加 `--require-effects`,要求业务效果也通过。 +报告分别给出 `configuration`、`models`、`build`、`effects` 和 `ready_for_biz`。普通 `check` 的退出码表示配置/资产/构建检查;发布门禁应增加 `--require-effects`,要求业务效果也通过。 ## 构建变体 diff --git a/include/core/pipeline_validator.h b/include/core/pipeline_validator.h index bc26e41c..5b986d38 100644 --- a/include/core/pipeline_validator.h +++ b/include/core/pipeline_validator.h @@ -27,7 +27,6 @@ struct ValidationFix { // 由开发工具 (alg_pipeline_tool) 填写;Validator 本身只报告中性诊断。 struct ValidationRemediation { - int schema_version = 1; RemediationCause cause = RemediationCause::kUnknownConfigField; std::string summary; nlohmann::json facts = nlohmann::json::object(); diff --git a/models/asset_manifest.json b/models/asset_manifest.json index 22d24c23..87ae14b7 100644 --- a/models/asset_manifest.json +++ b/models/asset_manifest.json @@ -1,5 +1,4 @@ { - "schema_version": 1, "artifacts": { "qwen2.5-0.5b-instruct-q4_k_m.gguf": { "sha256": "74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db", diff --git a/scripts/generate_acceptance_evidence.sh b/scripts/generate_acceptance_evidence.sh index f099abdc..3b9fc330 100755 --- a/scripts/generate_acceptance_evidence.sh +++ b/scripts/generate_acceptance_evidence.sh @@ -53,7 +53,6 @@ import json import os evidence = { - "schema_version": 3, "scope": os.environ["EVIDENCE_SCOPE"], "generated_at_utc": datetime.datetime.now( datetime.timezone.utc).isoformat(), diff --git a/src/adapter/io_catalog.cpp b/src/adapter/io_catalog.cpp index 5f3cf504..388ef651 100644 --- a/src/adapter/io_catalog.cpp +++ b/src/adapter/io_catalog.cpp @@ -119,7 +119,6 @@ nlohmann::json IoCatalog::ToJson(const PipelineCatalogSnapshot& snapshot, } auto result = PipelineCatalog::ToJson(snapshot, biz_filter); - result["schema_version"] = 4; result["input_converters"] = std::move(input_converters); result["output_converters"] = std::move(output_converters); result["io_bindings"] = std::move(io_bindings); diff --git a/src/adapter/io_catalog.h b/src/adapter/io_catalog.h index a4de3f0e..92f89e59 100644 --- a/src/adapter/io_catalog.h +++ b/src/adapter/io_catalog.h @@ -11,7 +11,7 @@ namespace llm_edgeflow { * @brief Integration 层的 Catalog 聚合门面 (Schema Version 4) * * 聚合 Core 的 PipelineCatalogSnapshot 与 Integration 的 - * IoConverterRegistry 及 IoBindingRegistry,生成对外统一 Schema 4 Catalog + * IoConverterRegistry 及 IoBindingRegistry,生成对外统一 Catalog * JSON。 */ class IoCatalog { diff --git a/src/adapter/operator/operator_config_resolver.cpp b/src/adapter/operator/operator_config_resolver.cpp index 0d117b96..d0264b43 100644 --- a/src/adapter/operator/operator_config_resolver.cpp +++ b/src/adapter/operator/operator_config_resolver.cpp @@ -316,7 +316,7 @@ int OperatorConfigResolver::Resolve( return ret; } - // 读取并解析部署配置文件 (Schema 1) + // 读取并解析部署配置文件 DeploymentIoConfig dep_config; std::string dep_err; if (!DeploymentIoConfig::ReadFromFile(full_cfg.string(), &dep_config, diff --git a/src/cli/alg_pipeline_tool.cpp b/src/cli/alg_pipeline_tool.cpp index a8468419..b54bf760 100644 --- a/src/cli/alg_pipeline_tool.cpp +++ b/src/cli/alg_pipeline_tool.cpp @@ -36,8 +36,7 @@ using llm_edgeflow::PipelineValidator; namespace fs = std::filesystem; nlohmann::json PipelineError(DiagnosticCode code, const std::string& message) { - return {{"schema_version", 1}, - {"ok", false}, + return {{"ok", false}, {"diagnostics", nlohmann::json::array({{{"code", DiagnosticCodeName(code)}, {"path", "/"}, @@ -47,8 +46,7 @@ nlohmann::json PipelineError(DiagnosticCode code, const std::string& message) { nlohmann::json ToolError(const std::string& code, const std::string& message, const std::string& path = "/") { - return {{"schema_version", 1}, - {"ok", false}, + return {{"ok", false}, {"diagnostics", nlohmann::json::array({{{"code", code}, {"path", path}, {"message", message}, @@ -260,9 +258,7 @@ nlohmann::json ResolveConf(const std::string& file, const std::string& root, {"effective_pipeline", std::move(effective)}, {"model_paths", std::move(paths)}, {"output_pools", OutputPoolsJson(*resolved.io_plan)}}; - return {{"schema_version", 1}, - {"ok", true}, - {"configuration", std::move(configuration)}}; + return {{"ok", true}, {"configuration", std::move(configuration)}}; } void Usage() { @@ -404,7 +400,6 @@ int main(int argc, char* argv[]) { return 1; } auto result = PipelineCatalog::NodeToJson(*definition); - result["schema_version"] = 3; result["ok"] = true; std::cout << result.dump(2) << std::endl; return 0; @@ -437,7 +432,6 @@ int main(int argc, char* argv[]) { } result = PipelineCatalog::BackendToJson(*definition); } - result["schema_version"] = 1; result["ok"] = true; std::cout << result.dump(2) << std::endl; return 0; @@ -509,10 +503,9 @@ int main(int argc, char* argv[]) { return 1; } } - auto result = raw ? std::move(pipeline) - : nlohmann::json({{"schema_version", 1}, - {"ok", true}, - {"pipeline", std::move(pipeline)}}); + auto result = + raw ? std::move(pipeline) + : nlohmann::json({{"ok", true}, {"pipeline", std::move(pipeline)}}); std::cout << result.dump(2) << std::endl; return 0; } @@ -631,7 +624,6 @@ int main(int argc, char* argv[]) { std::string diag_path = diag.path.empty() ? "/" : diag.path; std::string diag_msg = error.empty() ? diag.message : error; nlohmann::json err_res = { - {"schema_version", 1}, {"ok", false}, {"diagnostics", nlohmann::json::array({{{"code", "IO_VALIDATION_ERROR"}, @@ -658,8 +650,7 @@ int main(int argc, char* argv[]) { ? ExternalType(plan->output_converter->external_slots) : ""}}; - nlohmann::json result = {{"schema_version", 1}, - {"ok", true}, + nlohmann::json result = {{"ok", true}, {"binding", std::move(binding_info)}, {"output_pools", OutputPoolsJson(*plan)}, {"diagnostics", nlohmann::json::array()}}; @@ -705,7 +696,6 @@ int main(int argc, char* argv[]) { input_str.append(buffer, std::cin.gcount()); if (input_str.size() > 4 * 1024 * 1024) { nlohmann::json err_res = { - {"schema_version", 1}, {"ok", false}, {"diagnostics", {{{"code", "AUTHORING_ERROR"}, diff --git a/src/cli/pipeline_authoring.cpp b/src/cli/pipeline_authoring.cpp index 551eba0a..67416265 100644 --- a/src/cli/pipeline_authoring.cpp +++ b/src/cli/pipeline_authoring.cpp @@ -173,7 +173,7 @@ nlohmann::json AuthoringChange::ToJson() const { } nlohmann::json AuthoringResult::ToJson() const { - nlohmann::json j = {{"schema_version", schema_version}, {"ok", ok}}; + nlohmann::json j = {{"ok", ok}}; if (pipeline.has_value()) { j["pipeline"] = *pipeline; } @@ -1087,18 +1087,11 @@ AuthoringResult PipelineAuthoring::ApplyRequest(const nlohmann::json& request) { return result; } - CheckFields(request, {"schema_version", "pipeline", "operation", - "operations", "require_valid"}); + CheckFields(request, + {"pipeline", "operation", "operations", "require_valid"}); if (request.contains("require_valid") && !request["require_valid"].is_boolean()) throw std::invalid_argument("require_valid 必须是布尔值"); - if (!request.contains("schema_version") || - !request["schema_version"].is_number_integer() || - request["schema_version"] != 1) { - result.ok = false; - result.diagnostics.push_back("schema_version 必须为 1"); - return result; - } if (!request.contains("pipeline") || !request["pipeline"].is_object()) { result.ok = false; result.diagnostics.push_back("缺少 pipeline 对象"); diff --git a/src/cli/pipeline_authoring.h b/src/cli/pipeline_authoring.h index e5b2e0ab..4523858a 100644 --- a/src/cli/pipeline_authoring.h +++ b/src/cli/pipeline_authoring.h @@ -20,7 +20,6 @@ struct AuthoringChange { }; struct AuthoringResult { - int schema_version = 1; bool ok = false; std::optional pipeline; std::vector changes; @@ -33,8 +32,7 @@ struct AuthoringResult { class PipelineAuthoring { public: - // 应用单个编辑请求,包含 {schema_version, pipeline, operation/operations, - // require_valid}。 + // 应用单个编辑请求,包含 {pipeline, operation/operations, require_valid}。 static AuthoringResult ApplyRequest(const nlohmann::json& request); // 将一个编辑操作应用到内存中的 Pipeline 文档。 diff --git a/src/cli/pipeline_document_validation.cpp b/src/cli/pipeline_document_validation.cpp index 00e66b93..cf208193 100644 --- a/src/cli/pipeline_document_validation.cpp +++ b/src/cli/pipeline_document_validation.cpp @@ -29,9 +29,7 @@ DocumentValidationResult ValidatePipelineDocument( {"message", diag.message}, {"severity", "error"}}; nlohmann::json resp = { - {"schema_version", 1}, - {"ok", false}, - {"diagnostics", nlohmann::json::array({diag_item})}}; + {"ok", false}, {"diagnostics", nlohmann::json::array({diag_item})}}; if (mode == DocumentValidationMode::kPlan) { resp["plan"] = {{"layers", nlohmann::json::array()}, {"topological_order", nlohmann::json::array()}}; diff --git a/src/cli/pipeline_remediation.cpp b/src/cli/pipeline_remediation.cpp index 308ee3af..5f48c96f 100644 --- a/src/cli/pipeline_remediation.cpp +++ b/src/cli/pipeline_remediation.cpp @@ -162,7 +162,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, if (def && diag->path.rfind(prefix, 0) == 0) { std::string field_name = diag->path.substr(prefix.size()); ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kUnknownConfigField; rem.summary = "节点 '" + diag->node_id + "' 的配置包含未知字段 '" + field_name + "'。"; @@ -201,7 +200,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, } if (def && !field_name.empty()) { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kMissingConfigField; rem.summary = "节点 '" + diag->node_id + "' 缺少必填配置字段 '" + field_name + "'。"; @@ -248,7 +246,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, } if (def && !field_name.empty()) { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kInvalidConfigValue; rem.summary = "节点 '" + diag->node_id + "' 的配置项 '" + field_name + "' 值不合法。"; @@ -303,7 +300,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, std::string req_cap = dep.capability; ValidationRemediation rem; - rem.schema_version = 1; rem.cause = (diag->code == DiagnosticCode::kUnknownModelReference) ? RemediationCause::kUnknownModelReference : RemediationCause::kModelCapabilityMismatch; @@ -406,7 +402,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, if (producer_idx >= 0 && producer_out_type != expected_type) { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kPortTypeMismatch; rem.summary = "生产者 '" + producer_id + "' 输出类型与端口 '" + port_name + "' 要求不符。"; @@ -425,7 +420,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, diag->remediation = std::move(rem); } else { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kNoCompatibleInputSource; rem.summary = bound_key.empty() @@ -469,7 +463,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, } if (!dup_dep.empty()) { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kDuplicateDependency; rem.summary = "节点 '" + diag->node_id + "' 包含重复依赖 '" + dup_dep + "'。"; @@ -524,7 +517,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, } ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kUnknownDependency; rem.summary = "节点 '" + diag->node_id + "' 依赖了未知的节点 ID '" + dep_id + "'。"; @@ -534,7 +526,6 @@ void PopulateBasicRemediation(ValidationDiagnostic* diag, } } else if (diag->code == DiagnosticCode::kMissingBizOutput) { ValidationRemediation rem; - rem.schema_version = 1; rem.cause = RemediationCause::kMissingBizOutput; rem.summary = "Pipeline 未产出 biz '" + root.value("biz_name", "") + "' 所需的输出 '" + diag->port + "'。"; diff --git a/src/core/pipeline_validator.cpp b/src/core/pipeline_validator.cpp index d2b69d6c..2e13204e 100644 --- a/src/core/pipeline_validator.cpp +++ b/src/core/pipeline_validator.cpp @@ -1160,8 +1160,7 @@ nlohmann::json ValidationRemediation::ToJson() const { for (const auto& fix : fixes) { fixes_json.push_back(fix.ToJson()); } - return {{"schema_version", schema_version}, - {"cause", RemediationCauseName(cause)}, + return {{"cause", RemediationCauseName(cause)}, {"summary", summary}, {"facts", facts}, {"fixes", std::move(fixes_json)}}; @@ -1188,8 +1187,7 @@ nlohmann::json ValidationReport::ToJson() const { for (const auto& diagnostic : diagnostics) { items.push_back(diagnostic.ToJson()); } - return {{"schema_version", 1}, - {"ok", ok}, + return {{"ok", ok}, {"diagnostics", std::move(items)}, {"plan", {{"topological_order", topological_order}, diff --git a/tests/contract/architecture/test_quality_gate_contract.py b/tests/contract/architecture/test_quality_gate_contract.py index 60d1a712..cb2d5431 100644 --- a/tests/contract/architecture/test_quality_gate_contract.py +++ b/tests/contract/architecture/test_quality_gate_contract.py @@ -565,7 +565,6 @@ def main(): "success", "failure", "cancelled"], env=evidence_env) assert result.returncode == 0, result.stdout + result.stderr report = json.loads(evidence.read_text()) - assert report["schema_version"] == 3 gates = report["gates"] assert gates == {"canonical_run_all_tests": "success", "ci_runtime_address_undefined_sanitizer": "failure", diff --git a/tests/contract/catalog/test_catalog_contract_ssot.cpp b/tests/contract/catalog/test_catalog_contract_ssot.cpp index 1a14cf41..74ef76ec 100644 --- a/tests/contract/catalog/test_catalog_contract_ssot.cpp +++ b/tests/contract/catalog/test_catalog_contract_ssot.cpp @@ -223,7 +223,6 @@ TEST_F(CatalogContractSsotTest, // 5. 验证 PipelineCatalog::ToJson 序列化规范性与过滤逻辑 TEST_F(CatalogContractSsotTest, ToJsonSerializationAndFiltering) { auto full_catalog = PipelineCatalog::ToJson(); - EXPECT_FALSE(full_catalog.contains("schema_version")); EXPECT_TRUE(full_catalog["nodes"].is_array()); EXPECT_FALSE(full_catalog.contains("engines")); EXPECT_TRUE(full_catalog["models"].is_array()); @@ -246,7 +245,6 @@ TEST_F(CatalogContractSsotTest, ToJsonSerializationAndFiltering) { // 业务过滤查询 auto km_catalog = PipelineCatalog::ToJson("keyword_match"); - EXPECT_FALSE(km_catalog.contains("schema_version")); EXPECT_FALSE(km_catalog["nodes"].empty()); EXPECT_EQ(km_catalog["bizs"].size(), 1U); EXPECT_EQ(km_catalog["bizs"][0]["biz_name"], "keyword_match"); @@ -263,10 +261,9 @@ TEST_F(CatalogContractSsotTest, ToJsonSerializationAndFiltering) { EXPECT_TRUE(found_match_node); } -// 5b. 验证 IoCatalog Schema 4 聚合与对外规范性 -TEST_F(CatalogContractSsotTest, IoCatalogSchema4SerializationAndFiltering) { +// 5b. 验证 IoCatalog 聚合与对外规范性 +TEST_F(CatalogContractSsotTest, IoCatalogSerializationAndFiltering) { auto full_catalog = IoCatalog::ToJson(); - EXPECT_EQ(full_catalog["schema_version"], 4); EXPECT_TRUE(full_catalog["nodes"].is_array()); EXPECT_TRUE(full_catalog["models"].is_array()); EXPECT_TRUE(full_catalog["backends"].is_array()); @@ -277,7 +274,6 @@ TEST_F(CatalogContractSsotTest, IoCatalogSchema4SerializationAndFiltering) { // 业务过滤查询 auto km_catalog = IoCatalog::ToJson("keyword_match"); - EXPECT_EQ(km_catalog["schema_version"], 4); EXPECT_TRUE(km_catalog["bizs"].is_array()); EXPECT_TRUE(km_catalog["nodes"].is_array()); EXPECT_TRUE(km_catalog["input_converters"].is_array()); @@ -378,7 +374,6 @@ TEST_F(CatalogContractSsotTest, ASSERT_TRUE(registry.RegisterOutputConverter(output)); const auto catalog = IoCatalog::ToJson(); - EXPECT_EQ(catalog.at("schema_version"), 4); const auto& inputs = catalog.at("input_converters"); const auto input_it = std::find_if( inputs.begin(), inputs.end(), [](const nlohmann::json& converter) { diff --git a/tests/fixtures/asset_manifest_test.json b/tests/fixtures/asset_manifest_test.json index c3625ee2..0b732a01 100644 --- a/tests/fixtures/asset_manifest_test.json +++ b/tests/fixtures/asset_manifest_test.json @@ -1,5 +1,4 @@ { - "schema_version": 1, "artifacts": { "demo/fixtures/mock/artifacts/neutral-llm.fixture": { "sha256": "1082efc03cb9ab1c572b7cf4dfe0388949c93c0be791da3d96308546542c928f", diff --git a/tests/fixtures/effects/entity_mock_exact.json b/tests/fixtures/effects/entity_mock_exact.json index 341e9f9d..79292e2e 100644 --- a/tests/fixtures/effects/entity_mock_exact.json +++ b/tests/fixtures/effects/entity_mock_exact.json @@ -1,5 +1,4 @@ { - "schema_version": 1, "name": "entity-neutral-model-contract", "io_binding": "entity_extract", "dataset": "../../../data/corpus_entity_extract.txt", diff --git a/tests/fixtures/effects/keyword_exact.json b/tests/fixtures/effects/keyword_exact.json index aac28cf9..fecee990 100644 --- a/tests/fixtures/effects/keyword_exact.json +++ b/tests/fixtures/effects/keyword_exact.json @@ -1,5 +1,4 @@ { - "schema_version": 1, "name": "keyword-selected-config", "io_binding": "keyword_match", "dataset": "keyword_inputs.txt", diff --git a/tests/fixtures/pipelines/validation/invalid_pipeline_cases.json b/tests/fixtures/pipelines/validation/invalid_pipeline_cases.json index cdef25d0..dd411ff1 100644 --- a/tests/fixtures/pipelines/validation/invalid_pipeline_cases.json +++ b/tests/fixtures/pipelines/validation/invalid_pipeline_cases.json @@ -1,5 +1,4 @@ { - "schema_version": 1, "cases": [ { "name": "unknown_business", diff --git a/tests/integration/demo/test_demo_runner.cpp b/tests/integration/demo/test_demo_runner.cpp index 87b8081b..93dc27d2 100644 --- a/tests/integration/demo/test_demo_runner.cpp +++ b/tests/integration/demo/test_demo_runner.cpp @@ -390,7 +390,7 @@ TEST(DemoRunnerTest, ProfileOwnsExecutionSettings) { {"depth", 8}}; auto write_profile = [&](const nlohmann::json& value) { std::ofstream(path) << nlohmann::json( - {{"schema_version", 2}, {"profiles", {{"execution", value}}}}); + {{"profiles", {{"execution", value}}}}); }; write_profile(profile); DemoOptions cli, merged; @@ -462,8 +462,7 @@ TEST(DemoRunnerTest, ProfileRejectsUnknownFieldsAndInvalidShapes) { {nullptr, "must be an object"}}; for (const auto& [profile, diagnostic] : cases) { SCOPED_TRACE(profile.dump()); - std::ofstream(path) << nlohmann::json{{"schema_version", 2}, - {"profiles", {{"invalid", profile}}}}; + std::ofstream(path) << nlohmann::json{{"profiles", {{"invalid", profile}}}}; nlohmann::json profiles; std::string error; EXPECT_EQ(LoadAndValidateProfilesDocument(path.string(), &profiles, &error), @@ -489,10 +488,10 @@ TEST(DemoRunnerTest, ProfileSchemaStrictValidation) { std::string temp_invalid_json = "./results/invalid_profile.json"; std::filesystem::create_directories("./results"); - // Case 1: 缺少 schema_version + // Case 1: 缺少 profiles 映射 { std::ofstream ofs(temp_invalid_json); - ofs << "{\"profiles\": {}}"; + ofs << "{}"; } DemoOptions cli_opts; cli_opts.profile = "foo"; @@ -500,12 +499,12 @@ TEST(DemoRunnerTest, ProfileSchemaStrictValidation) { std::string err; EXPECT_EQ(LoadAndMergeProfiles(temp_invalid_json, cli_opts, &merged, &err), 3); + EXPECT_NE(err.find("profiles"), std::string::npos); // Case 2: 非法 suite 字符串 { std::ofstream ofs(temp_invalid_json); ofs << R"({ - "schema_version": 2, "profiles": { "bad_prof": { "config": "demo/fixtures/mock/pipeline_entity_extract.conf", @@ -524,7 +523,6 @@ TEST(DemoRunnerTest, ProfileSchemaStrictValidation) { { std::ofstream ofs(temp_invalid_json); ofs << R"({ - "schema_version": 2, "profiles": { "bad_suite_type": { "config": "configs/pipeline_keyword_match_rules.conf", @@ -542,7 +540,6 @@ TEST(DemoRunnerTest, ProfileSchemaStrictValidation) { { std::ofstream ofs(temp_invalid_json); ofs << R"({ - "schema_version": 2, "profiles": { "overflow_prof": { "config": "demo/fixtures/mock/pipeline_entity_extract.conf", @@ -737,7 +734,6 @@ TEST(DemoRunnerTest, ResultWriterAtomicOutputAndCumulativeAppend) { if (!line.empty()) { count++; auto obj = nlohmann::json::parse(line); - EXPECT_EQ(obj["schema_version"], 1); EXPECT_EQ(obj["profile"], "test_profile_unit"); EXPECT_EQ(obj["biz"], "unit_test"); if (obj["request_id"] == 9002) { @@ -753,7 +749,6 @@ TEST(DemoRunnerTest, ResultWriterAtomicOutputAndCumulativeAppend) { std::ifstream s_ifs(summary_path); nlohmann::json summary_obj; s_ifs >> summary_obj; - EXPECT_EQ(summary_obj["schema_version"], 1); EXPECT_EQ(summary_obj["biz"], "unit_test"); EXPECT_EQ(summary_obj["total_samples"], 2); EXPECT_EQ(summary_obj["success_count"], 1); @@ -1079,7 +1074,6 @@ TEST(DemoRunnerTest, ControlCommandCliAndProfilePrecedence) { KiteDemoDirectory temporary; auto write_profile = [&](const nlohmann::json& cmd) { const nlohmann::json document = { - {"schema_version", 2}, {"profiles", {{"control", {{"config", "configs/pipeline_keyword_match_rules.conf"}, diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 4efc4455..8e5f3a90 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -1282,16 +1282,11 @@ TEST_F(OperatorApiTest, OutputsConfigValidationFailClosed) { void* handle = nullptr; - // 0. 旧 Schema 1 配置格式严格拒绝 -> -2 + // 0. .conf 只允许 pipe_path,未知字段严格拒绝 -> -2 { std::ofstream ofs(conf_path); - ofs << R"({ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_keyword_match_rules.json", - "io_binding": "keyword_match" - } - })"; + ofs << R"({"pipe_path": "pipeline_keyword_match_rules.json", + "extra_field": 1})"; } EXPECT_EQ(ops_.Create(&handle, ¶m), -2); EXPECT_NE(std::string(GetOperatorLastError()).find("Unknown field"), diff --git a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp index 0fba0ac1..d768919a 100644 --- a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp +++ b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp @@ -632,7 +632,6 @@ TEST(PipelineValidatorTest, TableDrivenParityMatrix) { ASSERT_TRUE(stream.is_open()); nlohmann::json fixtures; stream >> fixtures; - ASSERT_EQ(fixtures["schema_version"], 1); for (const auto& test : fixtures["cases"]) { SCOPED_TRACE(test["name"].get()); @@ -845,7 +844,6 @@ TEST(PipelineValidatorTest, ExplainReturnsCandidateFixForUnknownConfigField) { ASSERT_NE(target_diag, nullptr); ASSERT_TRUE(target_diag->remediation.has_value()); const auto& rem = *target_diag->remediation; - EXPECT_EQ(rem.schema_version, 1); EXPECT_EQ(rem.cause, RemediationCause::kUnknownConfigField); EXPECT_EQ(rem.facts.value("field", ""), "temprature"); @@ -997,7 +995,6 @@ TEST(PipelineValidatorTest, ValidateProducesBasicRemediation) { } ASSERT_NE(target_diag, nullptr); ASSERT_TRUE(target_diag->remediation.has_value()); - EXPECT_EQ(target_diag->remediation->schema_version, 1); EXPECT_NE(RemediationCauseName(target_diag->remediation->cause), "UNKNOWN"); EXPECT_FALSE(target_diag->remediation->summary.empty()); EXPECT_FALSE(target_diag->remediation->facts.empty()); diff --git a/tests/tooling/studio_fix_workflow_test.mjs b/tests/tooling/studio_fix_workflow_test.mjs index 1cbb87c3..1cffc370 100644 --- a/tests/tooling/studio_fix_workflow_test.mjs +++ b/tests/tooling/studio_fix_workflow_test.mjs @@ -30,7 +30,6 @@ const document = { addEventListener() {}, documentElement: new Element(), body: new Element(), }; let previewResponse, previewCalls = 0, confirmations = 0, validationCalls = 0; -let catalogVersion = 4; let authoringResponse, authoringCalls = [], preflightResponse; const requests = []; const context = vm.createContext({ @@ -41,7 +40,7 @@ const context = vm.createContext({ location: { search: "", hash: "" } }, Option: class extends Element { constructor(text, value) { super("option"); this.textContent = text; this.value = value; } }, async testApi(path) { - if (path.startsWith("/catalog")) return { schema_version: catalogVersion, nodes: [], models: [], profiles: [], bizs: [], io_bindings: [] }; + if (path.startsWith("/catalog")) return { nodes: [], models: [], profiles: [], bizs: [], io_bindings: [] }; if (path === "/profiles") return { profiles: [] }; if (path === "/pipelines") return { pipelines: [] }; if (path === "/assets") return { selections: [], variants: [] }; @@ -86,7 +85,7 @@ function reset() { editing: true, catalogReady: true, pipelineVersion: 1, documentVersion: 1, savedPipeline: JSON.stringify(initial), dirty: false, deployment: null, modelPathActions: {}, preflight: null, validationReport: { revision: "revision-1", tool_fingerprint: "tool-1", diagnostics: [ - { code: "TEST", remediation: { schema_version: 1, fixes: [fix] } }, + { code: "TEST", remediation: { fixes: [fix] } }, ] }, }); drafts.clear(); history.reset({ pipeline: state.pipeline, selected: "" }); @@ -216,33 +215,3 @@ for (const change of [ "Delayed preflight must not display a result for another draft or deployment"); } console.log("Studio native authoring, page approval and preflight snapshot regressions passed"); - -// 为不支持的线格式版本渲染实际的诊断卡片。 -const { appendDiagnostic } = modules.get("editor.js").namespace; -for (const schema_version of [0, 2, 999]) { - const container = new Element(); - appendDiagnostic(container, { code: "TEST", message: "Readable old diagnostic", suggestions: ["Manual action"], - remediation: { schema_version, summary: "future schema", fixes: [fix] } }, () => {}, () => {}); - const flatten = node => [node, ...node.children.flatMap(flatten)]; - assert.equal(flatten(container).filter(node => node.tagName === "BUTTON").length, 0, - "Unknown remediation versions must not expose an Apply button"); - assert.ok(flatten(container).some(node => node.textContent === "Readable old diagnostic")); -} -console.log("Studio real repair handler, history, late response and schema-version checks passed"); - -// 覆盖两条 Catalog 入口路径和真实的校验守卫。 -for (const opened of [false, true]) { - reset(); - if (!opened) state.pipeline = null; - catalogVersion = 3; - const beforeCalls = validationCalls; - await app.namespace.refreshLists(); - assert.equal(state.catalogReady, false, "Reject old Catalog on startup and on an open document"); - assert.match(elements.get("#toast").children[0].textContent, /Catalog v4/); - assert.equal(await app.namespace.validate(), false); - assert.equal(validationCalls, beforeCalls, "No request after an incompatible Catalog"); - catalogVersion = 4; - await app.namespace.loadCatalog(); - assert.equal(state.catalogReady, true, "A compatible Catalog restores readiness"); -} -console.log("Studio Catalog version rejection and recovery passed"); diff --git a/tests/tooling/test_dev_recipe.py b/tests/tooling/test_dev_recipe.py index ada0bf75..8bdcc063 100644 --- a/tests/tooling/test_dev_recipe.py +++ b/tests/tooling/test_dev_recipe.py @@ -99,7 +99,6 @@ def test_list_recipes_has_stable_machine_report(self): with contextlib.redirect_stdout(stream): self.assertEqual(RECIPE.list_recipes(True), 0) report = json.loads(stream.getvalue()) - self.assertEqual(report["schema_version"], 1) self.assertTrue(report["ok"]) self.assertEqual(set(report["recipes"]), {"prompt-config", "text-llm-node"}) @@ -128,17 +127,6 @@ def test_entity_prompt_uses_pinned_assets_and_real_business_expectation(self): result = self.verify() self.assertTrue(result["ok"], result) - def test_native_requires_current_catalog(self): - for version in (None, 3, 4, 5): - report = {"ok": True, "schema_version": version} - result = subprocess.CompletedProcess([], 0, json.dumps(report), "") - with self.subTest(version=version), mock.patch.object(RECIPE.subprocess, "run", return_value=result): - if version == 4: - self.assertEqual(RECIPE.native(TOOL, ["catalog"], self.root), report) - else: - with self.assertRaisesRegex(RECIPE.RecipeError, "requires Catalog v4"): - RECIPE.native(TOOL, ["catalog"], self.root) - def test_effect_conf_requires_exact_nonempty_pipe_path(self): spec = self.root / "effect.json" spec.write_text(json.dumps({"dataset": "data/input.json"})) diff --git a/tests/tooling/test_pipeline_studio.py b/tests/tooling/test_pipeline_studio.py index bcfe262e..c6e23e49 100644 --- a/tests/tooling/test_pipeline_studio.py +++ b/tests/tooling/test_pipeline_studio.py @@ -568,8 +568,6 @@ def command(self, *args, input_pipeline=None): check=False, ) payload = json.loads(process.stdout) - expected_schema = 4 if args[0] == "catalog" and payload.get("ok") else (3 if args[0] == "describe-node" and payload.get("ok") else 1) - self.assertEqual(payload["schema_version"], expected_schema) return process.returncode, payload def test_production_tool_explains_unknown_registrations(self): @@ -604,7 +602,7 @@ def test_edit_explains_unknown_registrations(self): for require_valid in (False, True): with self.subTest(require_valid=require_valid): request = { - "schema_version": 1, "pipeline": fixture, "require_valid": require_valid, + "pipeline": fixture, "require_valid": require_valid, "operation": {"kind": "rename_node", "node_id": "custom_prompt", "new_id": "renamed_prompt"}, } @@ -632,7 +630,7 @@ def test_edit_explains_unknown_registrations(self): for operation in ({"kind": "rename_node", "node_id": node_id, "new_id": "renamed_rule"}, {"kind": "remove_node", "node_id": node_id}): with self.subTest(operation=operation): - request = {"schema_version": 1, "pipeline": keyword, "require_valid": False, + request = {"pipeline": keyword, "require_valid": False, "operation": operation} process = subprocess.run([str(production), "edit", "--stdin"], cwd=ROOT, input=json.dumps(request), text=True, capture_output=True) @@ -792,7 +790,7 @@ def test_invalid_profile_fields_and_shapes_are_rejected_by_all_consumers(self): profile_path = root / "demo/profiles.json" for profile, expected, field in cases: with self.subTest(profile=profile): - profile_path.write_text(json.dumps({"schema_version": 2, "profiles": {"invalid": profile}})) + profile_path.write_text(json.dumps({"profiles": {"invalid": profile}})) for args in (("catalog",), ("init", "--io-binding", "keyword_match", "--profile", "invalid")): process = subprocess.run([str(PIPELINE_TOOL), *args], cwd=root, capture_output=True, text=True) self.assertEqual(process.returncode, 1, process.stderr) @@ -823,7 +821,7 @@ def test_describe_models_and_backends_match_catalog(self): with self.subTest(command=command, name=definition[type_field]): code, described = self.command(command, definition[type_field]) self.assertEqual(code, 0) - self.assertEqual(described, {**definition, "schema_version": 1, "ok": True}) + self.assertEqual(described, {**definition, "ok": True}) def test_describe_models_and_backends_reject_unknown_names(self): for command, diagnostic in ( @@ -1203,7 +1201,6 @@ def test_cli_edit_invalid_deployment_validation_policy(self): # 情形 1:require_valid=false -> 返回修改后的草稿,validation.ok=false req_false = { - "schema_version": 1, "pipeline": invalid_doc, "operation": op, "require_valid": False, @@ -1220,7 +1217,6 @@ def test_cli_edit_invalid_deployment_validation_policy(self): # 情形 2:require_valid=true -> 拒绝,且不返回修改后的 Pipeline req_true = { - "schema_version": 1, "pipeline": invalid_doc, "operation": op, "require_valid": True, @@ -1240,7 +1236,6 @@ def test_cli_file_failures_return_json_diagnostics(self): code, res = self.command("validate-io", "nonexistent_config_file.conf") self.assertEqual(code, 1) self.assertFalse(res["ok"]) - self.assertEqual(res["schema_version"], 1) self.assertEqual(res["diagnostics"][0]["code"], "IO_VALIDATION_ERROR") self.assertIn("nonexistent_config_file.conf", res["diagnostics"][0]["message"]) @@ -1248,7 +1243,6 @@ def test_cli_file_failures_return_json_diagnostics(self): code, res = self.command("resolve-conf", "nonexistent_config_file.conf", "--root", str(ROOT)) self.assertEqual(code, 1) self.assertFalse(res["ok"]) - self.assertEqual(res["schema_version"], 1) self.assertEqual(res["diagnostics"][0]["code"], "DEPLOYMENT_CONFIG") self.assertEqual(res["diagnostics"][0]["path"], "/") @@ -1256,7 +1250,6 @@ def test_cli_file_failures_return_json_diagnostics(self): code, res = self.command("validate", "nonexistent_pipeline.json") self.assertEqual(code, 1) self.assertFalse(res["ok"]) - self.assertEqual(res["schema_version"], 1) self.assertEqual(res["diagnostics"][0]["code"], "JSON_READ") def test_native_viewer_shows_data_mappings_and_extra_order(self): @@ -1395,7 +1388,6 @@ def test_local_development_api_and_static_modules(self): with self.post() as response: payload = json.load(response) self.assertTrue(payload["ok"]) - self.assertEqual(payload["schema_version"], 1) def test_initial_endpoint_only_exposes_explicit_startup_document(self): with urllib.request.urlopen(self.base + "/initial?filename=/etc/passwd", timeout=5) as response: @@ -1689,7 +1681,6 @@ def test_invalid_fixtures_table_driven_parity_matrix(self): / "invalid_pipeline_cases.json" ) fixtures = json.loads(fixture_path.read_text(encoding="utf-8")) - self.assertEqual(fixtures["schema_version"], 1) for case in fixtures["cases"]: with self.subTest(case=case["name"]): @@ -1822,7 +1813,7 @@ def test_asset_hashes_include_sidecars_and_changed_paths_are_unregistered(self): (root / "weights.bin").write_bytes(b"test weights") (root / "vocab.txt").write_bytes(b"tokenizer") model = {"model_id": "m", "model_type": "bge_embedding", "backend": "onnxruntime", "model_path": "weights.bin", "model_config": {"tokenizer_file": "vocab.txt"}} - manifest = {"schema_version": 1, "selections": [{"id": "test", "model": json.loads(json.dumps(model)), "paths": {"/model_path": "weights.bin", "/model_config/tokenizer_file": "vocab.txt"}, "files": ["weights.bin", "vocab.txt"]}], "artifacts": {name: {"sha256": selection.file_digest(root / name)} for name in ("weights.bin", "vocab.txt")}} + manifest = {"selections": [{"id": "test", "model": json.loads(json.dumps(model)), "paths": {"/model_path": "weights.bin", "/model_config/tokenizer_file": "vocab.txt"}, "files": ["weights.bin", "vocab.txt"]}], "artifacts": {name: {"sha256": selection.file_digest(root / name)} for name in ("weights.bin", "vocab.txt")}} pipeline = {"models": [model]} incomplete = json.loads(json.dumps(manifest)) incomplete["selections"][0]["files"] = [] @@ -1852,7 +1843,7 @@ def test_asset_paths_use_host_root_and_sidecars_use_model_directory(self): template = copy.deepcopy(model) template["model_path"] = "nested/weights.bin" template["model_config"]["tokenizer_file"] = "nested/vocab.txt" - manifest = {"schema_version": 1, "selections": [{"id": "nested", "model": template, + manifest = {"selections": [{"id": "nested", "model": template, "paths": {"/model_path": "nested/weights.bin", "/model_config/tokenizer_file": "nested/vocab.txt"}, "files": ["nested/weights.bin", "nested/vocab.txt"]}], "artifacts": {name: {"sha256": selection.file_digest(assets / name)} @@ -1901,14 +1892,12 @@ def test_native_build_variant_and_real_effects_are_bound_to_selection(self): report = selection.inspect_selection(pipeline, tool, ROOT / "models") self.assertTrue(report["ok"]) self.assertFalse(report["ready_for_biz"]) - self.assertEqual(report["schema_version"], 2) self.assertNotIn("ready_for_business", report) # 当前规范构建启用了若干 Backend;声明为空的 minimal 变体必须失败, # 与这个无模型的 Pipeline 无关。 mismatched = selection.inspect_selection(pipeline, tool, ROOT / "models", variant="minimal" if report["build"]["enabled_backends"] else "default-cpu") self.assertFalse(mismatched["ok"]) receipt = selection.evaluate(pipeline, report, tool, ROOT / "models", spec, conf, demo) - self.assertEqual(receipt["schema_version"], 2) self.assertEqual(receipt["metrics"]["pass_rate"], 1.0) self.assertEqual(receipt["metrics"]["total"], 4) with tempfile.TemporaryDirectory() as directory: @@ -2052,7 +2041,7 @@ def test_authoring_connect_preserves_valid_default_output_fanout(self): pipe["pipeline"] = [source, existing, consumer] self.assertTrue(self.service.validate(pipe)["ok"]) code, result = self.run_edit({ - "schema_version": 1, "pipeline": pipe, "require_valid": True, + "pipeline": pipe, "require_valid": True, "operation": {"kind": "connect", "source": {"node_id": "a", "port": "text"}, "target": {"node_id": "c", "port": "text"}}, @@ -2086,7 +2075,7 @@ def test_authoring_inferred_cycle_is_rejected_by_native_validation(self): for require_valid in (False, True): with self.subTest(operation=operation["kind"], require_valid=require_valid): code, result = self.run_edit({ - "schema_version": 1, "pipeline": pipe, + "pipeline": pipe, "require_valid": require_valid, "operation": operation, }) self.assertEqual(code, 1 if require_valid else 0, result) @@ -2115,7 +2104,7 @@ def test_authoring_disconnect_rejects_unknown_and_reversed_endpoints(self): for source, target in cases: with self.subTest(source=source, target=target): self.assert_edit_rejected({ - "schema_version": 1, "pipeline": pipe, + "pipeline": pipe, "operation": {"kind": "disconnect", "source": source, "target": target}, }, 0) @@ -2133,15 +2122,14 @@ def test_authoring_rejects_ambiguous_duplicate_node_targets(self): ] for operation in operations: with self.subTest(operation=operation): - self.assert_edit_rejected({"schema_version": 1, "pipeline": pipe, + self.assert_edit_rejected({"pipeline": pipe, "operation": operation}, 0) def test_authoring_rejects_invalid_request_fields_without_crashing(self): - base = {"schema_version": 1, "pipeline": self.keyword, + base = {"pipeline": self.keyword, "operation": {"kind": "add_node", "node_type": "TextTemplateNode"}} requests = [ dict(base, requrie_valid=True), dict(base, require_valid="true"), - dict(base, schema_version=True), dict(base, schema_version=1.0), dict(base, operations=None), dict(base, operation=None, operations=[base["operation"]]), dict(base, operations={}), dict(base, pipeline=[]), ] @@ -2165,7 +2153,7 @@ def test_authoring_batch_schema_errors_report_index_and_no_candidate(self): for operation in invalid_operations: with self.subTest(operation=operation): self.assert_edit_rejected({ - "schema_version": 1, "pipeline": self.keyword, + "pipeline": self.keyword, "operations": [ {"kind": "add_node", "node_type": "TextTemplateNode", "id": "added"}, operation, @@ -2174,7 +2162,6 @@ def test_authoring_batch_schema_errors_report_index_and_no_candidate(self): def test_authoring_add_node_explicit_auto_id_and_collision(self): req = { - "schema_version": 1, "pipeline": {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []}, "operation": { "kind": "add_node", @@ -2193,7 +2180,6 @@ def test_authoring_add_node_explicit_auto_id_and_collision(self): # 拒绝重复 ID req2 = { - "schema_version": 1, "pipeline": res["pipeline"], "operation": { "kind": "add_node", @@ -2208,7 +2194,6 @@ def test_authoring_add_node_explicit_auto_id_and_collision(self): # 自动分配 ID req3 = { - "schema_version": 1, "pipeline": res["pipeline"], "operation": { "kind": "add_node", @@ -2222,7 +2207,6 @@ def test_authoring_add_node_explicit_auto_id_and_collision(self): # 未知 Node 类型 req4 = { - "schema_version": 1, "pipeline": res["pipeline"], "operation": {"kind": "add_node", "node_type": "NonExistentNode"}, } @@ -2253,7 +2237,6 @@ def test_authoring_remove_node_detaches_inputs_and_dependencies(self): ], } req = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "remove_node", "node_id": "template"}, } @@ -2290,7 +2273,6 @@ def test_authoring_rename_node_syncs_dependencies_and_preserves_keys(self): ], } req = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "rename_node", @@ -2311,7 +2293,6 @@ def test_authoring_rename_node_syncs_dependencies_and_preserves_keys(self): # 拒绝重复 ID req_dup = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "rename_node", "node_id": "template", "new_id": "rule"}, } @@ -2342,7 +2323,6 @@ def test_authoring_connect_and_cycle_and_redundant_dependencies(self): } # 入口到 Node req1 = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "connect", @@ -2360,7 +2340,6 @@ def test_authoring_connect_and_cycle_and_redundant_dependencies(self): # Node 到 Node req2 = { - "schema_version": 1, "pipeline": res1["pipeline"], "operation": { "kind": "connect", @@ -2378,7 +2357,6 @@ def test_authoring_connect_and_cycle_and_redundant_dependencies(self): # 环检测:把 rule 连到 template 会形成环 req_cycle = { - "schema_version": 1, "pipeline": res2["pipeline"], "operation": { "kind": "connect", @@ -2414,7 +2392,6 @@ def test_authoring_connect_and_disconnect_biz_egress(self): } # 把 rule.matches 连接到 $egress rule_matches req_egress = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "connect", @@ -2431,7 +2408,6 @@ def test_authoring_connect_and_disconnect_biz_egress(self): # 断开 rule.matches 与 $egress rule_matches 的连接 req_disc = { - "schema_version": 1, "pipeline": res_egress["pipeline"], "operation": { "kind": "disconnect", @@ -2469,7 +2445,6 @@ def test_authoring_disconnect_preserves_extra_order_and_removes_input(self): } # 断开数据端口时保留执行依赖 (回归用例 1) req = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "disconnect", @@ -2485,7 +2460,6 @@ def test_authoring_disconnect_preserves_extra_order_and_removes_input(self): # remove_dependency 移除执行依赖 req_rm_dep = { - "schema_version": 1, "pipeline": res["pipeline"], "operation": { "kind": "remove_dependency", @@ -2500,7 +2474,6 @@ def test_authoring_disconnect_preserves_extra_order_and_removes_input(self): def test_authoring_batch_operations_and_atomic_rollback(self): pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req_valid = { - "schema_version": 1, "pipeline": pipe, "operations": [ {"kind": "add_node", "node_type": "TextTemplateNode", "id": "t1", "config": {"template": "{{primary}}"}}, @@ -2519,7 +2492,6 @@ def test_authoring_batch_operations_and_atomic_rollback(self): # 批处理中途失败时丢弃所有修改 req_fail = { - "schema_version": 1, "pipeline": pipe, "operations": [ {"kind": "add_node", "node_type": "TextTemplateNode", "id": "t1"}, @@ -2535,7 +2507,6 @@ def test_authoring_batch_operations_and_atomic_rollback(self): # 批大小超过 128 req_oversize = { - "schema_version": 1, "pipeline": pipe, "operations": [{"kind": "add_node", "node_type": "TextTemplateNode"}] * 129, } @@ -2546,7 +2517,6 @@ def test_authoring_batch_operations_and_atomic_rollback(self): def test_authoring_regression_case_2_key_allocation_no_collision(self): pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req = { - "schema_version": 1, "pipeline": pipe, "operations": [ {"kind": "add_node", "node_type": "TextRuleMatchNode", "id": "foo"}, @@ -2755,7 +2725,6 @@ def test_authoring_oversized_payload_rejection_4mib(self): pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} large_comment = "x" * (4 * 1024 * 1024 + 100) req = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "add_node", "node_type": "TextRuleMatchNode"}, "comment": large_comment, @@ -2768,7 +2737,6 @@ def test_authoring_oversized_payload_rejection_4mib(self): def test_authoring_reserved_node_ids_rejected(self): pipe = {"deployment": {"io": {"io_binding": "keyword_match"}}, "models": [], "pipeline": []} req_add_ingress = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "add_node", "node_type": "TextRuleMatchNode", "id": "$ingress"}, } @@ -2785,7 +2753,6 @@ def test_authoring_reserved_node_ids_rejected(self): ], } req_rename_egress = { - "schema_version": 1, "pipeline": pipe2, "operation": {"kind": "rename_node", "node_id": "node_a", "new_id": "$egress"}, } @@ -2817,7 +2784,6 @@ def test_authoring_disconnect_requires_explicit_binding(self): ], } req_disc = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "disconnect", @@ -2845,7 +2811,6 @@ def test_authoring_disconnect_requires_explicit_binding(self): ], } req_ing_disc = { - "schema_version": 1, "pipeline": pipe_ing, "operation": { "kind": "disconnect", @@ -2882,7 +2847,6 @@ def test_authoring_disconnect_egress_syncs_explicit_consumers(self): ], } req = { - "schema_version": 1, "pipeline": pipe, "operation": { "kind": "disconnect", @@ -2921,7 +2885,6 @@ def test_authoring_remove_node_handles_consumers_and_egress(self): ], } req = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "remove_node", "node_id": "rule"}, } @@ -2943,7 +2906,6 @@ def test_authoring_add_dependency_avoids_redundant_indirect_ancestor(self): } # 添加 c 依赖 a:a 已经通过 b 成为 c 的间接祖先 req = { - "schema_version": 1, "pipeline": pipe, "operation": {"kind": "add_dependency", "node_id": "c", "depends_on_id": "a"}, } diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 7cc42fd3..73dc7290 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -1262,13 +1262,12 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { EXPECT_NE(error.find("Unknown or unregistered io_binding"), std::string::npos); - // 3. DeploymentIoConfig schema 校验拒绝非法/旧格式 - nlohmann::json invalid_version_json = { - {"schema_version", 999}, - {"data", {{"pipe_path", "test.json"}, {"io_binding", "keyword_match"}}}}; + // 3. DeploymentIoConfig schema 校验拒绝未知字段 + nlohmann::json unknown_field_json = {{"pipe_path", "test.json"}, + {"extra_field", 1}}; DeploymentIoConfig parsed_cfg; - EXPECT_FALSE(DeploymentIoConfig::Parse(invalid_version_json, ".", &parsed_cfg, - &error)); + EXPECT_FALSE( + DeploymentIoConfig::Parse(unknown_field_json, ".", &parsed_cfg, &error)); EXPECT_NE(error.find("Unknown field"), std::string::npos); // 4. 一致性检查拒绝含未知输出槽位的 Operator 配置 diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index be8a9c72..19c4ed67 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -380,17 +380,11 @@ TEST_F(IoBindingRegistryTest, DeploymentIoConfigValidation) { EXPECT_TRUE(DeploymentIoConfig::Parse(valid_cfg, tmp_dir, &parsed, &err)); EXPECT_EQ(parsed.pipe_path, "test.json"); - // 2. 拒绝旧 Schema 1 包装 (schema_version + data) - nlohmann::json old_schema1 = { - {"schema_version", 1}, - {"data", {{"pipe_path", "test.json"}, {"io_binding", "test_biz"}}}}; - EXPECT_FALSE(DeploymentIoConfig::Parse(old_schema1, tmp_dir, &parsed, &err)); - EXPECT_NE(err.find("Unknown field"), std::string::npos); - // 3. 拒绝顶层未知字段 nlohmann::json bad_field = valid_cfg; bad_field["extra_field"] = "foo"; EXPECT_FALSE(DeploymentIoConfig::Parse(bad_field, tmp_dir, &parsed, &err)); + EXPECT_NE(err.find("Unknown field"), std::string::npos); // 5. 路径逃逸拒绝 nlohmann::json escape_cfg = {{"pipe_path", "../../../etc/passwd"}}; @@ -423,11 +417,11 @@ TEST_F(IoBindingRegistryTest, DeploymentIoConfigValidation) { "Error in config file " + bad_conf_path + ": ", 0), 0); - // 8. ReadFromFile 针对旧 Schema 1 报错同样携带配置文件路径上下文 - const std::string dep_conf_path = tmp_dir + "/deprecated.conf"; + // 8. ReadFromFile 针对未知字段报错同样携带配置文件路径上下文 + const std::string dep_conf_path = tmp_dir + "/unknown_field.conf"; { std::ofstream ofs(dep_conf_path); - ofs << old_schema1.dump(); + ofs << bad_field.dump(); } EXPECT_FALSE(DeploymentIoConfig::ReadFromFile(dep_conf_path, &parsed, &read_err, &read_diag)); diff --git a/tools/dev_recipe.py b/tools/dev_recipe.py index 7061106e..a82000bf 100644 --- a/tools/dev_recipe.py +++ b/tools/dev_recipe.py @@ -115,7 +115,7 @@ def get_profile_data(profile_name, root=ROOT): def list_recipes(as_json=False): if as_json: - print(json.dumps({"schema_version": 1, "ok": True, "recipes": SUPPORTED_RECIPES})) + print(json.dumps({"ok": True, "recipes": SUPPORTED_RECIPES})) else: for name, recipe in SUPPORTED_RECIPES.items(): print(f"{name}: {recipe['title']}\n {recipe['description']}") @@ -132,10 +132,6 @@ def native(tool, arguments, root, document=None): raise RecipeError(f"Native tool returned no JSON: {process.stdout}\n{process.stderr}") from error if process.returncode or not report.get("ok", False): raise RecipeError("Native " + arguments[0] + " failed", report) - if arguments and arguments[0] == "catalog": - schema_version = report.get("schema_version") - if schema_version != 4: - raise RecipeError(f"Unsupported Catalog schema version {schema_version}; dev_recipe requires Catalog v4", report) return report @@ -178,7 +174,7 @@ def command(description, argv): def result(recipe, completed, pending, artifacts=(), error=None, step=None, **extra): - report = {"schema_version": 1, "ok": error is None, "recipe": recipe, + report = {"ok": error is None, "recipe": recipe, "completed_steps": completed, "pending_steps": pending, "failed_step": step if error is not None else None, "artifacts": [str(p) for p in artifacts], "next_commands": []} @@ -211,8 +207,8 @@ def effects_inputs(profile_name, pipeline, root, effects_path, model_root, manif def check_effects(spec): samples = spec.get("samples", []) - if spec.get("schema_version") != 1 or not samples: - raise RecipeError("Effects requires schema_version=1 and nonempty labelled samples") + if not samples: + raise RecipeError("Effects requires nonempty labelled samples") ids = set() for sample in samples: request_id = sample.get("request_id") diff --git a/tools/pipeline_studio/README.md b/tools/pipeline_studio/README.md index 95fb7409..9aae0106 100644 --- a/tools/pipeline_studio/README.md +++ b/tools/pipeline_studio/README.md @@ -132,7 +132,7 @@ Web 服务。需要经过校验的拓扑顺序和波前层时,应使用 在“运行”页展开“接入契约”,可核对当前方案的 `io_binding`、输入/输出 Converter 及槽位名称。Pipeline 只通过 `deployment.io.io_binding` 选择外部 I/O 契约; Demo 从 SDK 解析结果选择运行函数,Profile 仅保存配置、数据集和运行选项。 -Catalog v4 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` 和有效 +Catalog 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` 和有效 `key_suffix`,分别表示逻辑槽、宿主类型、类型注册后缀和外部键后缀。工具未提供的字段 显示为“未提供”;应使用与工作区匹配的 `alg_pipeline_tool`。详情中的内部端口 与槽位类型不代表外部 JSON 载荷协议,完整请求响应仍以对应 Converter 契约为准。 @@ -146,8 +146,8 @@ Catalog v4 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` ./build/alg_pipeline_tool plan configs/pipeline_doc_qa_default.json ``` -可将版本化编辑请求交给 `alg_pipeline_tool edit --stdin`,返回候选 Pipeline、变更和校验报告; -请求包含整数 `schema_version: 1`、完整 Pipeline 文档 `pipeline`,以及互斥的单动作对象 +可将编辑请求交给 `alg_pipeline_tool edit --stdin`,返回候选 Pipeline、变更和校验报告; +请求包含完整 Pipeline 文档 `pipeline`,以及互斥的单动作对象 `operation` 或非空动作数组 `operations`。可选布尔字段 `require_valid` 默认为 `false`; 设为 `true` 时要求最终候选合法。单次最多 128 个动作、4 MiB。 @@ -175,7 +175,7 @@ Catalog v4 的 `external_slots` 导出 `slot_name`、`type_id`、`type_suffix` ### 校验工具选择 -CLI 克隆默认返回包含 `pipeline` 的版本化响应。需要直接保存 Pipeline JSON 时使用 +CLI 克隆默认返回包含 `ok` 与 `pipeline` 的响应。需要直接保存 Pipeline JSON 时使用 `init --io-binding --profile --raw`,确认命令成功后再对保存文件执行 `validate`。`--empty --raw` 生成待填写草稿;`--empty` 与 `--profile` 不能同时指定。 diff --git a/tools/pipeline_studio/server.py b/tools/pipeline_studio/server.py index d62026b9..a5a0a6d0 100755 --- a/tools/pipeline_studio/server.py +++ b/tools/pipeline_studio/server.py @@ -72,7 +72,7 @@ def server_bind(self) -> None: def json_result(ok: bool, **values: Any) -> dict[str, Any]: - return {"schema_version": 1, "ok": ok, **values} + return {"ok": ok, **values} def revision_for(data: bytes) -> str: @@ -409,7 +409,6 @@ def preview_authoring( raise StudioError("TOOL_OUTDATED", "操作期间工具已更新,请重新加载", 409) req: dict[str, Any] = { - "schema_version": 1, "pipeline": pipeline, "require_valid": require_valid, } @@ -637,7 +636,7 @@ def demo_command(profile: dict[str, Any], conf_path: Path, output_dir: Path, con profile_path = output_dir / "demo-profile.json" if profile_path.is_symlink(): raise StudioError("SYMLINK_REJECTED", "拒绝覆盖符号链接运行 Profile", 409) - profile_path.write_text(json.dumps({"schema_version": 2, "profiles": {name: run_profile}}, + profile_path.write_text(json.dumps({"profiles": {name: run_profile}}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") return [ str(DEMO_BINARY), "--profiles-file", str(profile_path), "--profile", name, diff --git a/tools/pipeline_studio/web/app.js b/tools/pipeline_studio/web/app.js index 6dc11192..f7f77be0 100644 --- a/tools/pipeline_studio/web/app.js +++ b/tools/pipeline_studio/web/app.js @@ -303,15 +303,6 @@ async function loadCatalog(binding = "") { return catalogRequests.run( () => api(`/catalog${binding ? `?io_binding=${encodeURIComponent(binding)}` : ""}`), catalog => { - if (catalog.schema_version !== 4) { - state.catalogReady = false; - const msg = `不支持的 Catalog 版本 (v${catalog.schema_version}),Pipeline Studio 要求 Catalog v4。请升级或重新构建后端工具。`; - toast(msg, true); - clearValidation(msg); - renderOperators(); - renderAll(); - return; - } state.catalog = catalog; state.catalogReady = true; renderOperators(); renderAll(); } ); @@ -426,15 +417,6 @@ async function refreshLists() { await loadCatalog(pipelineBinding(state.pipeline)); } else { catalogRequests.invalidate(); - if (allCatalog.schema_version !== 4) { - state.catalogReady = false; - const msg = `不支持的 Catalog 版本 (v${allCatalog.schema_version}),Pipeline Studio 要求 Catalog v4。请升级或重新构建后端工具。`; - toast(msg, true); - clearValidation(msg); - renderOperators(); - renderAll(); - return; - } state.catalog = allCatalog; state.catalogReady = true; renderOperators(); diff --git a/tools/pipeline_studio/web/editor.js b/tools/pipeline_studio/web/editor.js index 714ec849..90c0241a 100644 --- a/tools/pipeline_studio/web/editor.js +++ b/tools/pipeline_studio/web/editor.js @@ -48,7 +48,7 @@ export function appendDiagnostic(container, item, selectNode, onPreviewFix) { if (item.remediation) { const rem = item.remediation; if (rem.summary) appendText("div", `原因诊断:${rem.summary}`); - if (rem.schema_version === 1 && Array.isArray(rem.fixes) && rem.fixes.length > 0) { + if (Array.isArray(rem.fixes) && rem.fixes.length > 0) { const fixesTitle = document.createElement("div"); fixesTitle.className = "fixes-title"; fixesTitle.textContent = "可选修复操作:"; diff --git a/tools/verify_selection.py b/tools/verify_selection.py index 363f910f..fc8985c9 100644 --- a/tools/verify_selection.py +++ b/tools/verify_selection.py @@ -90,8 +90,8 @@ def build_run_conf(pipeline, outputs, pipe_path, model_root, bundle_root, pipeli def validate_manifest(manifest): - if not isinstance(manifest, dict) or manifest.get("schema_version") != 1: - raise ValueError("Unsupported asset manifest version") + if not isinstance(manifest, dict): + raise ValueError("Asset manifest must be a JSON object") artifacts = manifest["artifacts"] for name, artifact in artifacts.items(): if Path(name).is_absolute() or ".." in Path(name).parts or not re.fullmatch(r"[0-9a-f]{64}", artifact["sha256"]): @@ -186,7 +186,7 @@ def inspect_selection(pipeline, tool, model_root, manifest=MANIFEST, variant=Non build.update(expected_backends=expected, status="verified" if enabled == expected else "variant_mismatch") ok = validation.get("ok", False) and all(item["status"] == "verified" for item in assets) and build["status"] != "variant_mismatch" fingerprint = digest({"pipeline": pipeline, "assets": assets, "build": build}) - return {"schema_version": 2, "ok": bool(ok), "selection_fingerprint": fingerprint, + return {"ok": bool(ok), "selection_fingerprint": fingerprint, "configuration": validation, "build": build, "models": assets, "effects": {"status": "unverified"}, "ready_for_biz": False} @@ -279,7 +279,7 @@ def evaluate(pipeline, selection, tool, model_root, spec_path, conf_path, demo, if digest(effect_inputs(spec_path, conf_path, demo, pipeline_root)[3]) != test_fingerprint: raise ValueError("Effect inputs or binaries changed during execution") metrics = compare_samples(records, spec) - return {"schema_version": 2, "generated_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), + return {"generated_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(), "selection_fingerprint": selection["selection_fingerprint"], "test_fingerprint": test_fingerprint, "pipeline": execution_pipeline, "selection": selection, "test_inputs": test_inputs, "metrics": metrics, "records": records} From 8d314dd290d7a9a891c8317e34c4a10a6baa4f1e Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 17:16:32 +0800 Subject: [PATCH 4/4] refactor: drop version suffixes from model IDs and the Studio API path Model instance IDs in Pipeline configs, Demo fixtures, the asset manifest, docs and tests lose their version suffixes (embed_model_v1 and embed_model_v2 -> embed_model, llm_model_v1 -> llm_model, and so on); no Pipeline used two of them, so the shorter names stay unique per file. Test-only names follow (new_domain, other_biz, test_prompt). Pipeline Studio's HTTP endpoints move from /api/v1/... to /api/...; the server and web client ship together, so no version prefix is needed. Left as is: the external bge_base_zh_v1.5 model file name, CI cache-key salts, and the legacy-name denylist in the docs drift gate. Co-Authored-By: Claude Opus 5.5 --- configs/pipeline_audio_asr_intent_cpu.json | 4 +- configs/pipeline_cross_rerank_cpu.json | 4 +- configs/pipeline_dialogue_audit_default.json | 14 ++-- configs/pipeline_dialogue_audit_kite.json | 14 ++-- configs/pipeline_doc_qa_default.json | 10 +-- configs/pipeline_doc_qa_kite.json | 10 +-- ...line_doc_qa_kite_generated_embeddings.json | 10 +-- configs/pipeline_doc_qa_rerank_default.json | 14 ++-- configs/pipeline_ocr_invoice_qa_kite.json | 8 +-- .../mock/pipeline_audio_asr_intent.json | 4 +- .../mock/pipeline_dialogue_audit.json | 14 ++-- demo/fixtures/mock/pipeline_doc_qa.json | 10 +-- .../fixtures/mock/pipeline_doc_qa_custom.json | 4 +- .../fixtures/mock/pipeline_doc_qa_rerank.json | 14 ++-- .../mock/pipeline_ocr_invoice_qa.json | 8 +-- doc/CHANGELOG.md | 4 ++ doc/developer_guide.md | 2 +- models/asset_manifest.json | 16 ++--- src/core/pipeline_config.cpp | 2 +- .../abi/test_adapter_contract_security.cpp | 11 ++-- .../pipeline_cross_rerank_fixture.json | 4 +- .../pipeline_cross_rerank_missing_model.json | 4 +- tests/integration/demo/test_demo_runner.cpp | 6 +- .../operator/test_operator_api.cpp | 6 +- .../test_pipeline_catalog_validator.cpp | 4 +- tests/tooling/studio_browser_test.mjs | 20 +++--- tests/tooling/test_pipeline_studio.py | 14 ++-- .../unit/adapter/test_io_binding_registry.cpp | 2 +- .../test_definition_schema_validation.cpp | 2 +- tests/unit/core/test_framework_core.cpp | 12 ++-- .../core/test_node_ownership_and_reuse.cpp | 4 +- .../core/test_validated_pipeline_plan.cpp | 4 +- tests/unit/nodes/test_asr_transcribe_node.cpp | 14 ++-- tests/unit/nodes/test_common_nodes.cpp | 66 +++++++++---------- tests/unit/nodes/test_llm_generate_node.cpp | 25 ++++--- tests/unit/nodes/test_ocr_detect_node.cpp | 12 ++-- tests/unit/nodes/test_text_embedding_node.cpp | 30 ++++----- tests/unit/nodes/test_text_rerank_node.cpp | 4 +- tools/pipeline_studio/server.py | 40 +++++------ tools/pipeline_studio/web/api.js | 2 +- 40 files changed, 227 insertions(+), 225 deletions(-) diff --git a/configs/pipeline_audio_asr_intent_cpu.json b/configs/pipeline_audio_asr_intent_cpu.json index 1b26892a..2dd01297 100644 --- a/configs/pipeline_audio_asr_intent_cpu.json +++ b/configs/pipeline_audio_asr_intent_cpu.json @@ -6,7 +6,7 @@ }, "models": [ { - "model_id": "asr_model_v1", + "model_id": "asr_model", "model_type": "whisper_asr", "backend": "whisper_cpp", "model_path": "models/ggml-base.bin", @@ -25,7 +25,7 @@ "id": "transcribe", "node_type": "AsrTranscribeNode", "config": { - "bind_model": "asr_model_v1" + "bind_model": "asr_model" }, "inputs": { "audio": "audio_inputs" diff --git a/configs/pipeline_cross_rerank_cpu.json b/configs/pipeline_cross_rerank_cpu.json index 4c99ea3e..16c61513 100644 --- a/configs/pipeline_cross_rerank_cpu.json +++ b/configs/pipeline_cross_rerank_cpu.json @@ -6,7 +6,7 @@ }, "models": [ { - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_type": "bge_reranker", "backend": "onnxruntime", "model_path": "./models/ms_marco_tinybert_l2_v2_quantized.onnx", @@ -31,7 +31,7 @@ "id": "node_0_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 8 }, "inputs": { diff --git a/configs/pipeline_dialogue_audit_default.json b/configs/pipeline_dialogue_audit_default.json index 05c5f4a1..b23edce9 100644 --- a/configs/pipeline_dialogue_audit_default.json +++ b/configs/pipeline_dialogue_audit_default.json @@ -15,7 +15,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v2", + "model_id": "embed_model", "model_path": "./models/bge_base_zh_v1.5.onnx" }, { @@ -28,7 +28,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_path": "./models/ms_marco_tinybert_l2_v2_quantized.onnx" }, { @@ -47,7 +47,7 @@ "n_gpu_layers": 0, "check_tensors": false }, - "model_id": "audit_llm_v1", + "model_id": "audit_llm", "model_path": "./models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -92,7 +92,7 @@ "id": "node_2_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2" + "bind_model": "embed_model" }, "inputs": { "text": "user_texts" @@ -105,7 +105,7 @@ "id": "node_3_PolicyEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2", + "bind_model": "embed_model", "lifetime": "session" }, "inputs": { @@ -135,7 +135,7 @@ "id": "node_5_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 1 }, "inputs": { @@ -165,7 +165,7 @@ "id": "node_7_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "audit_llm_v1", + "bind_model": "audit_llm", "max_tokens": 256, "temperature": 0.1 }, diff --git a/configs/pipeline_dialogue_audit_kite.json b/configs/pipeline_dialogue_audit_kite.json index 013a9271..c225b0c3 100644 --- a/configs/pipeline_dialogue_audit_kite.json +++ b/configs/pipeline_dialogue_audit_kite.json @@ -15,7 +15,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v2", + "model_id": "embed_model", "model_path": "models/bge_base_zh_v1.5.onnx" }, { @@ -28,7 +28,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_path": "models/ms_marco_tinybert_l2_v2_quantized.onnx" }, { @@ -41,7 +41,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "audit_llm_v1", + "model_id": "audit_llm", "model_path": "models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -86,7 +86,7 @@ "id": "node_2_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2" + "bind_model": "embed_model" }, "inputs": { "text": "user_texts" @@ -99,7 +99,7 @@ "id": "node_3_PolicyEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2", + "bind_model": "embed_model", "lifetime": "session" }, "inputs": { @@ -129,7 +129,7 @@ "id": "node_5_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 1 }, "inputs": { @@ -159,7 +159,7 @@ "id": "node_7_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "audit_llm_v1", + "bind_model": "audit_llm", "max_tokens": 128, "temperature": 0.0 }, diff --git a/configs/pipeline_doc_qa_default.json b/configs/pipeline_doc_qa_default.json index 173f1423..37efa24a 100644 --- a/configs/pipeline_doc_qa_default.json +++ b/configs/pipeline_doc_qa_default.json @@ -15,7 +15,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "./models/bge_base_zh_v1.5.onnx" }, { @@ -34,7 +34,7 @@ "n_gpu_layers": 0, "check_tensors": false }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "./models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -57,7 +57,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -70,7 +70,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -148,7 +148,7 @@ "id": "node_6_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.3 }, diff --git a/configs/pipeline_doc_qa_kite.json b/configs/pipeline_doc_qa_kite.json index 264b9ce5..ea1e9cc3 100644 --- a/configs/pipeline_doc_qa_kite.json +++ b/configs/pipeline_doc_qa_kite.json @@ -15,7 +15,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "models/bge_base_zh_v1.5.onnx" }, { @@ -27,7 +27,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -50,7 +50,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -63,7 +63,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -141,7 +141,7 @@ "id": "node_6_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.0 }, diff --git a/configs/pipeline_doc_qa_kite_generated_embeddings.json b/configs/pipeline_doc_qa_kite_generated_embeddings.json index 8af2b934..7b9faa6a 100644 --- a/configs/pipeline_doc_qa_kite_generated_embeddings.json +++ b/configs/pipeline_doc_qa_kite_generated_embeddings.json @@ -19,7 +19,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "models/qwen2.5-0.5b-instruct-q4_k_m.gguf" }, { @@ -31,7 +31,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -54,7 +54,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -67,7 +67,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -145,7 +145,7 @@ "id": "node_6_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.0 }, diff --git a/configs/pipeline_doc_qa_rerank_default.json b/configs/pipeline_doc_qa_rerank_default.json index 31823f26..5ff8831c 100644 --- a/configs/pipeline_doc_qa_rerank_default.json +++ b/configs/pipeline_doc_qa_rerank_default.json @@ -15,7 +15,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "./models/bge_base_zh_v1.5.onnx" }, { @@ -28,7 +28,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_path": "./models/ms_marco_tinybert_l2_v2_quantized.onnx" }, { @@ -47,7 +47,7 @@ "n_gpu_layers": 0, "check_tensors": false }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "./models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -70,7 +70,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -83,7 +83,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -112,7 +112,7 @@ "id": "node_4_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 1 }, "inputs": { @@ -171,7 +171,7 @@ "id": "node_7_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.3 }, diff --git a/configs/pipeline_ocr_invoice_qa_kite.json b/configs/pipeline_ocr_invoice_qa_kite.json index 89925688..ba85ef05 100644 --- a/configs/pipeline_ocr_invoice_qa_kite.json +++ b/configs/pipeline_ocr_invoice_qa_kite.json @@ -15,7 +15,7 @@ "backend_config": { "run_config_file": "kite_vision_run.json" }, - "model_id": "ocr_model_v1", + "model_id": "ocr_model", "model_path": "models/SmolVLM-256M-Instruct-Q8_0.gguf" }, { @@ -27,7 +27,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "models/qwen2.5-0.5b-instruct-q4_k_m.gguf" } ], @@ -36,7 +36,7 @@ "id": "node_0_OcrDetectNode", "node_type": "OcrDetectNode", "config": { - "bind_model": "ocr_model_v1" + "bind_model": "ocr_model" }, "inputs": { "images": "image_paths" @@ -65,7 +65,7 @@ "id": "node_2_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 256, "temperature": 0.0 }, diff --git a/demo/fixtures/mock/pipeline_audio_asr_intent.json b/demo/fixtures/mock/pipeline_audio_asr_intent.json index 798cd846..ae889c1a 100644 --- a/demo/fixtures/mock/pipeline_audio_asr_intent.json +++ b/demo/fixtures/mock/pipeline_audio_asr_intent.json @@ -12,7 +12,7 @@ "max_batch_size": 2 }, "backend_config": {}, - "model_id": "asr_model_v1", + "model_id": "asr_model", "model_path": "./models/paraformer_asr_npu.bin" } ], @@ -21,7 +21,7 @@ "id": "node_0_AsrTranscribeNode", "node_type": "AsrTranscribeNode", "config": { - "bind_model": "asr_model_v1" + "bind_model": "asr_model" }, "inputs": { "audio": "audio_inputs" diff --git a/demo/fixtures/mock/pipeline_dialogue_audit.json b/demo/fixtures/mock/pipeline_dialogue_audit.json index a525f923..d531e519 100644 --- a/demo/fixtures/mock/pipeline_dialogue_audit.json +++ b/demo/fixtures/mock/pipeline_dialogue_audit.json @@ -13,7 +13,7 @@ "max_batch_size": 4 }, "backend_config": {}, - "model_id": "embed_model_v2", + "model_id": "embed_model", "model_path": "./models/bge_m3_npu.bin" }, { @@ -23,7 +23,7 @@ "max_batch_size": 4 }, "backend_config": {}, - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_path": "./models/bge_reranker_large_npu.bin" }, { @@ -34,7 +34,7 @@ "max_seq_len": 1024 }, "backend_config": {}, - "model_id": "audit_llm_v1", + "model_id": "audit_llm", "model_path": "./models/qwen_1_8b_npu.bin" } ], @@ -79,7 +79,7 @@ "id": "node_2_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2" + "bind_model": "embed_model" }, "inputs": { "text": "user_texts" @@ -92,7 +92,7 @@ "id": "node_3_PolicyEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v2", + "bind_model": "embed_model", "lifetime": "session" }, "inputs": { @@ -122,7 +122,7 @@ "id": "node_5_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 1 }, "inputs": { @@ -152,7 +152,7 @@ "id": "node_7_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "audit_llm_v1", + "bind_model": "audit_llm", "max_tokens": 256, "temperature": 0.1 }, diff --git a/demo/fixtures/mock/pipeline_doc_qa.json b/demo/fixtures/mock/pipeline_doc_qa.json index f61a6db1..609edec5 100644 --- a/demo/fixtures/mock/pipeline_doc_qa.json +++ b/demo/fixtures/mock/pipeline_doc_qa.json @@ -13,7 +13,7 @@ "max_batch_size": 4 }, "backend_config": {}, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "./models/bge_base_npu.bin" }, { @@ -24,7 +24,7 @@ "max_seq_len": 1024 }, "backend_config": {}, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "./models/qwen_1.5b_npu.bin" } ], @@ -47,7 +47,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -60,7 +60,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -138,7 +138,7 @@ "id": "node_6_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.3 }, diff --git a/demo/fixtures/mock/pipeline_doc_qa_custom.json b/demo/fixtures/mock/pipeline_doc_qa_custom.json index 3d8d66db..9a2db865 100644 --- a/demo/fixtures/mock/pipeline_doc_qa_custom.json +++ b/demo/fixtures/mock/pipeline_doc_qa_custom.json @@ -13,7 +13,7 @@ "max_seq_len": 1024 }, "backend_config": {}, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "demo/fixtures/mock/artifacts/neutral-llm.fixture" } ], @@ -71,7 +71,7 @@ "id": "custom_prompt", "node_type": "PromptGuidedLlmNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "prompt_template": "【参考片段】: {{context}}\n【用户提问】: {{input}}\n请基于参考片段给出精确解答:", "strip_markdown": true, "temperature": 0.3, diff --git a/demo/fixtures/mock/pipeline_doc_qa_rerank.json b/demo/fixtures/mock/pipeline_doc_qa_rerank.json index 39df66d0..3486ffe6 100644 --- a/demo/fixtures/mock/pipeline_doc_qa_rerank.json +++ b/demo/fixtures/mock/pipeline_doc_qa_rerank.json @@ -13,7 +13,7 @@ "max_batch_size": 4 }, "backend_config": {}, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "./models/bge_base_npu.bin" }, { @@ -23,7 +23,7 @@ "max_batch_size": 4 }, "backend_config": {}, - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_path": "./models/bge_reranker_large_npu.bin" }, { @@ -34,7 +34,7 @@ "max_seq_len": 1024 }, "backend_config": {}, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "./models/qwen_1.5b_npu.bin" } ], @@ -57,7 +57,7 @@ "id": "node_1_QueryEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "raw_queries" @@ -70,7 +70,7 @@ "id": "node_2_ChunkEmbeddingNode", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1" + "bind_model": "embed_model" }, "inputs": { "text": "doc_chunks" @@ -99,7 +99,7 @@ "id": "node_4_TextRerankNode", "node_type": "TextRerankNode", "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 1 }, "inputs": { @@ -158,7 +158,7 @@ "id": "node_7_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.3 }, diff --git a/demo/fixtures/mock/pipeline_ocr_invoice_qa.json b/demo/fixtures/mock/pipeline_ocr_invoice_qa.json index 833b778a..f061eeb9 100644 --- a/demo/fixtures/mock/pipeline_ocr_invoice_qa.json +++ b/demo/fixtures/mock/pipeline_ocr_invoice_qa.json @@ -12,7 +12,7 @@ "max_batch_size": 2 }, "backend_config": {}, - "model_id": "ocr_model_v1", + "model_id": "ocr_model", "model_path": "./models/ch_ppocr_v4_det_rec_npu.bin" }, { @@ -23,7 +23,7 @@ "max_seq_len": 1024 }, "backend_config": {}, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "./models/qwen_1.5b_npu.bin" } ], @@ -32,7 +32,7 @@ "id": "node_0_OcrDetectNode", "node_type": "OcrDetectNode", "config": { - "bind_model": "ocr_model_v1" + "bind_model": "ocr_model" }, "inputs": { "images": "image_paths" @@ -61,7 +61,7 @@ "id": "node_2_LlmGenerateNode", "node_type": "LlmGenerateNode", "config": { - "bind_model": "llm_model_v1", + "bind_model": "llm_model", "max_tokens": 128, "temperature": 0.1 }, diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 8ca1b0a5..674b9fed 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +示例 Pipeline 配置、Demo 夹具与资产清单中的模型实例名去掉版本后缀(如 `embed_model_v1` / +`embed_model_v2` 改为 `embed_model`,`llm_model_v1` 改为 `llm_model`);Pipeline Studio 的 HTTP 接口 +由 `/api/v1/...` 改为 `/api/...`。 + 开发工具 JSON 不再携带 `schema_version`:`alg_pipeline_tool` 各命令输出、Catalog、校验报告与 remediation、`edit` 请求与响应、Pipeline Studio 接口、Demo 的 Profile 文件与结果文件、 `dev_recipe` / `verify_selection` 报告、效果规格、资产清单和验收证据都删除该字段及对应的版本检查; diff --git a/doc/developer_guide.md b/doc/developer_guide.md index b0b6f669..8ca5be2e 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -218,7 +218,7 @@ Pipeline 配置只使用 Model/Backend 语法: ```json { - "model_id": "embedding_v1", + "model_id": "embed_model", "model_type": "my_embedding_model", "backend": "my_tensor_backend", "model_path": "embedding/model.bin", diff --git a/models/asset_manifest.json b/models/asset_manifest.json index 87ae14b7..f79ef32d 100644 --- a/models/asset_manifest.json +++ b/models/asset_manifest.json @@ -95,7 +95,7 @@ "backend_config": { "max_batch_size": 4 }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "bge_base_zh_v1.5.onnx" }, "paths": { @@ -126,7 +126,7 @@ "n_gpu_layers": 0, "check_tensors": false }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "qwen2.5-0.5b-instruct-q4_k_m.gguf" }, "paths": { @@ -140,7 +140,7 @@ "id": "bge_reranker-onnxruntime", "label": "bge_reranker / onnxruntime", "model": { - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_type": "bge_reranker", "backend": "onnxruntime", "model_path": "ms_marco_tinybert_l2_v2_quantized.onnx", @@ -181,7 +181,7 @@ "backend_config": { "run_config_file": "kite_vision_run.json" }, - "model_id": "ocr_model_v1", + "model_id": "ocr_model", "model_path": "SmolVLM-256M-Instruct-Q8_0.gguf" }, "paths": { @@ -206,7 +206,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "llm_model_v1", + "model_id": "llm_model", "model_path": "qwen2.5-0.5b-instruct-q4_k_m.gguf" }, "paths": { @@ -222,7 +222,7 @@ "id": "whisper_asr-whisper_cpp", "label": "whisper_asr / whisper_cpp", "model": { - "model_id": "asr_model_v1", + "model_id": "asr_model", "model_type": "whisper_asr", "backend": "whisper_cpp", "model_path": "ggml-base.bin", @@ -259,7 +259,7 @@ "backend_config": { "run_config_file": "kite_text_run.json" }, - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_path": "qwen2.5-0.5b-instruct-q4_k_m.gguf" }, "paths": { @@ -275,7 +275,7 @@ "id": "whisper_asr-whisper_cpp-tiny", "label": "Whisper tiny Q5 / whisper_cpp", "model": { - "model_id": "asr_model_v1", + "model_id": "asr_model", "model_type": "whisper_asr", "backend": "whisper_cpp", "model_path": "ggml-tiny-q5_1.bin", diff --git a/src/core/pipeline_config.cpp b/src/core/pipeline_config.cpp index 5d358b08..64d53974 100644 --- a/src/core/pipeline_config.cpp +++ b/src/core/pipeline_config.cpp @@ -66,7 +66,7 @@ bool ParsePipelineConfig(const nlohmann::json& root, ParsedPipelineConfig result; - // 3. biz_name 是 v6 唯一业务标识字段,必须存在且为非空字符串。 + // 3. biz_name 是唯一业务标识字段,必须存在且为非空字符串。 if (shape::MissingRequired(root, structure, "biz_name")) { SetDiag(diagnostic, DiagnosticCode::kMissingField, "/biz_name", "Missing required field 'biz_name'"); diff --git a/tests/contract/abi/test_adapter_contract_security.cpp b/tests/contract/abi/test_adapter_contract_security.cpp index 9a8c58ad..b3037d59 100644 --- a/tests/contract/abi/test_adapter_contract_security.cpp +++ b/tests/contract/abi/test_adapter_contract_security.cpp @@ -512,15 +512,14 @@ TEST_F(AdapterContractSecurityTest, << runtime_err; ASSERT_NE(runtime, nullptr); - const auto embedding_registration = - runtime->GetPipeline() - ->GetSessionContext() - .GetModelManager() - .GetModelRegistration("embed_model_v1"); + const auto embedding_registration = runtime->GetPipeline() + ->GetSessionContext() + .GetModelManager() + .GetModelRegistration("embed_model"); const auto llm_registration = runtime->GetPipeline() ->GetSessionContext() .GetModelManager() - .GetModelRegistration("llm_model_v1"); + .GetModelRegistration("llm_model"); ASSERT_TRUE(embedding_registration.has_value()); ASSERT_TRUE(llm_registration.has_value()); EXPECT_EQ(embedding_registration->resolved_model_path, diff --git a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json index 0e1367df..db77be57 100644 --- a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json +++ b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_fixture.json @@ -6,7 +6,7 @@ }, "models": [ { - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_type": "bge_reranker", "backend": "onnxruntime", "model_path": "rerank_fixture.onnx", @@ -32,7 +32,7 @@ "node_type": "TextRerankNode", "depends_on": [], "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 8 }, "inputs": { diff --git a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json index 89a91318..370584c9 100644 --- a/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json +++ b/tests/fixtures/pipelines/cross_rerank/pipeline_cross_rerank_missing_model.json @@ -6,7 +6,7 @@ }, "models": [ { - "model_id": "rerank_model_v1", + "model_id": "rerank_model", "model_type": "bge_reranker", "backend": "onnxruntime", "model_path": "missing_rerank_fixture.onnx", @@ -32,7 +32,7 @@ "node_type": "TextRerankNode", "depends_on": [], "config": { - "bind_model": "rerank_model_v1", + "bind_model": "rerank_model", "top_k": 8 }, "inputs": { diff --git a/tests/integration/demo/test_demo_runner.cpp b/tests/integration/demo/test_demo_runner.cpp index 93dc27d2..e8df7922 100644 --- a/tests/integration/demo/test_demo_runner.cpp +++ b/tests/integration/demo/test_demo_runner.cpp @@ -599,10 +599,10 @@ TEST(DemoRunnerTest, RegistryLookupAndConflictDetection) { reg.ResetForTesting(); EXPECT_EQ(reg.Find("entity_extract"), nullptr); EXPECT_TRUE(reg.Register( - {"new_domain_v1", "Domain", [](const DemoOptions&) { return 0; }})); - const auto* added = reg.Find("new_domain_v1"); + {"new_domain", "Domain", [](const DemoOptions&) { return 0; }})); + const auto* added = reg.Find("new_domain"); ASSERT_NE(added, nullptr); - EXPECT_EQ(added->biz_name, "new_domain_v1"); + EXPECT_EQ(added->biz_name, "new_domain"); EXPECT_EQ(reg.Find("entity_extract"), nullptr); } diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 8e5f3a90..e5c71e08 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -247,12 +247,12 @@ TEST_F(OperatorApiTest, StronglyTypedControlValidation) { 0); // 3.2 ControlSwitchPromptParam 测试 - ControlSwitchPromptParam prompt_param_null{"prompt_v1", nullptr}; + ControlSwitchPromptParam prompt_param_null{"test_prompt", nullptr}; EXPECT_EQ( ops_.Control(handle, ControlCommand::kSwitchPrompt, &prompt_param_null), -2); - ControlSwitchPromptParam prompt_param_valid{"prompt_v2", + ControlSwitchPromptParam prompt_param_valid{"test_prompt", "用户提问:{query},请回答:"}; // KeywordMatch 不含 TextTemplateNode,返回 -7 // (COMPANY_ALG_ERR_UNSUPPORTED_CONTROL) @@ -303,7 +303,7 @@ TEST_F(OperatorApiTest, StronglyTypedControlValidation) { ASSERT_EQ(ops_.Create(&entity_handle, &entity_param), 0); ASSERT_NE(entity_handle, nullptr); - ControlSwitchPromptParam entity_prompt{"prompt_v2", "{{primary}}"}; + ControlSwitchPromptParam entity_prompt{"test_prompt", "{{primary}}"}; EXPECT_EQ(ops_.Control(entity_handle, ControlCommand::kSwitchPrompt, &entity_prompt), 0); diff --git a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp index d768919a..0e43f3c0 100644 --- a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp +++ b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp @@ -502,7 +502,7 @@ TEST(PipelineValidatorTest, ReportsConfigAndCapabilityErrors) { const nlohmann::json pipeline = { {"biz_name", "entity_extract"}, {"models", - {{{"model_id", "llm_model_v1"}, + {{{"model_id", "llm_model"}, {"model_type", "test_biz_embedding"}, {"backend", "test_tensor_backend"}, {"model_path", "fixture.bin"}, @@ -516,7 +516,7 @@ TEST(PipelineValidatorTest, ReportsConfigAndCapabilityErrors) { {"node_type", "LlmGenerateNode"}, {"depends_on", {"pre"}}, {"config", - {{"bind_model", "llm_model_v1"}, + {{"bind_model", "llm_model"}, {"max_tokens", 0}, {"invented", true}}}}, {{"id", "post"}, diff --git a/tests/tooling/studio_browser_test.mjs b/tests/tooling/studio_browser_test.mjs index 119b09bb..f58f8869 100644 --- a/tests/tooling/studio_browser_test.mjs +++ b/tests/tooling/studio_browser_test.mjs @@ -19,8 +19,8 @@ const errors = []; const authoringRequests = []; const initializationRequests = []; page.on("request", request => { - if (request.url().endsWith("/api/v1/authoring/preview")) authoringRequests.push(request.postDataJSON()); - if (request.url().endsWith("/api/v1/init")) initializationRequests.push(request.postDataJSON()); + if (request.url().endsWith("/api/authoring/preview")) authoringRequests.push(request.postDataJSON()); + if (request.url().endsWith("/api/init")) initializationRequests.push(request.postDataJSON()); }); page.on("pageerror", error => errors.push(error.message)); const screenshot = async name => { if (screenshotRoot) await page.screenshot({ path: join(screenshotRoot, `${name}.png`) }); }; @@ -107,7 +107,7 @@ try { "Contract labels and identifiers must wrap within their fields"); await screenshot("390-contract-details"); await page.setViewportSize({ width: 1366, height: 768 }); - await page.route("**/api/v1/catalog?io_binding=keyword_match", async route => { + await page.route("**/api/catalog?io_binding=keyword_match", async route => { const response = await route.fetch(); const catalog = await response.json(); for (const converter of [...catalog.input_converters, ...catalog.output_converters]) { @@ -150,7 +150,7 @@ try { let releasePreflight, sawPreflight; const heldPreflight = new Promise(resolve => { releasePreflight = resolve; }); const preflightStarted = new Promise(resolve => { sawPreflight = resolve; }); - await page.route("**/api/v1/preflight", async route => { + await page.route("**/api/preflight", async route => { sawPreflight(); await heldPreflight; await route.fulfill({ json: { ok: true, summary: { status: "ready", next_step: "STALE_PREFLIGHT_SENTINEL" } } }); }); @@ -159,7 +159,7 @@ try { releasePreflight(); await page.waitForFunction(() => !document.querySelector("#preflightSummary").classList.contains("loading")); assert.doesNotMatch(await page.locator("#preflightSummary").textContent(), /STALE_PREFLIGHT_SENTINEL/); - await page.unroute("**/api/v1/preflight"); + await page.unroute("**/api/preflight"); await page.locator("#runModelRoot").fill(modelRootBefore); await screenshot("run-results"); @@ -255,11 +255,11 @@ try { let releaseStart, started; const heldStart = new Promise(resolve => { releaseStart = resolve; }); const sawStart = new Promise(resolve => { started = resolve; }); - await page.route('**/api/v1/runs', async route => { + await page.route('**/api/runs', async route => { started(); await heldStart; await route.fulfill({ json: { ok: true, job_id: 'delayed', status: 'queued' } }); }); - await page.route('**/api/v1/runs/delayed', route => route.fulfill({ json: { ok: true, job: { + await page.route('**/api/runs/delayed', route => route.fulfill({ json: { ok: true, job: { status: 'completed', logs: 'ONLY_A', result: { 'results.jsonl': [{ request_id: 999, status: 0, output: { answer: 'ONLY_A' } }] }, } } })); await page.click('#openRunButton'); await page.click('#runButton'); await sawStart; @@ -269,7 +269,7 @@ try { assert.equal(await page.locator('#runResult').textContent(), ''); assert.equal(await page.locator('.run-sample').count(), 0); assert.doesNotMatch(await page.locator('#runLog').textContent(), /ONLY_A/); - await page.unroute('**/api/v1/runs'); await page.unroute('**/api/v1/runs/delayed'); + await page.unroute('**/api/runs'); await page.unroute('**/api/runs/delayed'); await page.click('#runButton'); await page.waitForFunction(() => document.querySelector('#runSummary').textContent.includes('运行已完成')); assert.equal(await page.locator('.run-sample').count(), 2); @@ -327,7 +327,7 @@ try { // 启动失败信息在超过旧的 2.6 秒超时后仍必须可读。 const startupPage = await browser.newPage(); try { - await startupPage.route('**/api/v1/assets', route => route.fulfill({ status: 503, + await startupPage.route('**/api/assets', route => route.fulfill({ status: 503, json: { ok: false, error: { code: 'ASSET_CATALOG_FAILED', message: '启动故障测试' } }, })); await startupPage.goto(url); @@ -338,7 +338,7 @@ try { assert.match(await startupPage.locator('#toast').textContent(), /启动故障测试/); await startupPage.click('#toastDismiss'); assert.equal(await startupPage.locator('#toast').evaluate(el => el.classList.contains('show')), false); - await startupPage.unroute('**/api/v1/assets'); await startupPage.reload(); + await startupPage.unroute('**/api/assets'); await startupPage.reload(); await startupPage.waitForFunction(() => document.querySelector('#pipelineSelect').options.length > 1); assert.equal(await startupPage.locator('#toast').evaluate(el => el.classList.contains('error')), false); } finally { await startupPage.close(); } diff --git a/tests/tooling/test_pipeline_studio.py b/tests/tooling/test_pipeline_studio.py index c6e23e49..05e5f928 100644 --- a/tests/tooling/test_pipeline_studio.py +++ b/tests/tooling/test_pipeline_studio.py @@ -1322,7 +1322,7 @@ def setUp(self): self.skipTest("sandbox forbids binding a loopback test socket") self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) self.thread.start() - self.base = f"http://127.0.0.1:{self.server.server_address[1]}/api/v1" + self.base = f"http://127.0.0.1:{self.server.server_address[1]}/api" def tearDown(self): self.server.shutdown() @@ -1369,7 +1369,7 @@ def test_authoring_http_rejects_unknown_fields_and_conflicting_revisions(self): authoring.assert_not_called() def test_local_development_api_and_static_modules(self): - origin = self.base.removesuffix("/api/v1") + origin = self.base.removesuffix("/api") with urllib.request.urlopen(origin + "/index.html", timeout=5) as response: index = response.read().decode() self.assertIn('type="module" src="app.js"', index) @@ -1439,15 +1439,15 @@ def test_web_api_preserves_proxy_paths_and_reports_non_json_responses(self): for (const page of ["", "index.html?view=1#pipeline=chosen"]) { location.href = base + page; assert.equal((await api("/pipelines")).pipelines[0].filename, "pipeline_test.json"); - assert.equal(requested, base + "api/v1/pipelines"); + assert.equal(requested, base + "api/pipelines"); await api("/catalog?io_binding=doc_qa"); - assert.equal(requested, base + "api/v1/catalog?io_binding=doc_qa"); + assert.equal(requested, base + "api/catalog?io_binding=doc_qa"); } } body = JSON.stringify({ ok: false, error: { code: "INVALID_JSON", message: "invalid pipeline" } }); status = 400; await assert.rejects(api("/pipeline"), error => error.status === 400 && - error.message.includes("invalid pipeline") && error.message.includes("/api/v1/pipeline") && + error.message.includes("invalid pipeline") && error.message.includes("/api/pipeline") && error.message.includes("HTTP 400") && error.message.includes("INVALID_JSON") && error.payload.error.code === "INVALID_JSON"); status = 200; await assert.rejects(api("/validate"), /invalid pipeline/); @@ -1462,13 +1462,13 @@ def test_web_api_preserves_proxy_paths_and_reports_non_json_responses(self): [200, '{"incomplete":', "incomplete"]]) { status = code; body = text; await assert.rejects(api("/initial"), error => error.status === code && - error.message.includes("/workspace/proxy/8080/api/v1/initial") && + error.message.includes("/workspace/proxy/8080/api/initial") && error.message.includes(`HTTP ${code}`) && error.message.includes(summary)); } status = 500; body = "Not Found " + "x".repeat(1000) + "END_OF_BODY"; await assert.rejects(api("/initial"), error => !error.message.includes("END_OF_BODY")); globalThis.fetch = async () => { throw new TypeError("Failed to fetch"); }; -await assert.rejects(api("/catalog"), error => error.message.includes("/api/v1/catalog") && error.message.includes("Failed to fetch")); +await assert.rejects(api("/catalog"), error => error.message.includes("/api/catalog") && error.message.includes("Failed to fetch")); """ process = subprocess.run( [shutil.which("node"), "--input-type=module", "-e", script, str(WEB_ROOT / "api.js")], diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index 19c4ed67..8778831b 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -1218,7 +1218,7 @@ TEST_F(IoBindingRegistryTest, DeploymentIoUnknownBindingOrMismatch_T07) { // 情形 2:即使 binding 存在,已移除的根业务标识也会被拒绝。 { nlohmann::json doc = base_doc; - doc["biz_name"] = "other_biz_v1"; + doc["biz_name"] = "other_biz"; EXPECT_FALSE(PrepareDeploymentDocument(doc, options, &prepared, &diag)); EXPECT_EQ(diag.code, "DEPLOYMENT_ERROR"); EXPECT_EQ(diag.path, "/biz_name"); diff --git a/tests/unit/core/test_definition_schema_validation.cpp b/tests/unit/core/test_definition_schema_validation.cpp index ec690261..8c9c1359 100644 --- a/tests/unit/core/test_definition_schema_validation.cpp +++ b/tests/unit/core/test_definition_schema_validation.cpp @@ -639,7 +639,7 @@ TEST_F(DefinitionSchemaValidationTest, auto with_default = definition; with_default.node_type = "DefaultModelReferenceNode"; - with_default.config_fields.front().default_value = "llm_model_v1"; + with_default.config_fields.front().default_value = "llm_model"; EXPECT_FALSE(ValidateNodeDefinitionStructure(with_default)); EXPECT_FALSE(NodeRegistry::Instance().Register( with_default.node_type, []() { return nullptr; }, with_default)); diff --git a/tests/unit/core/test_framework_core.cpp b/tests/unit/core/test_framework_core.cpp index c897ba27..0d7c07c8 100644 --- a/tests/unit/core/test_framework_core.cpp +++ b/tests/unit/core/test_framework_core.cpp @@ -104,20 +104,20 @@ TEST(TraceableItemTest, ProvenanceTracking) { TEST(ModelManagerTest, TypedModels) { ModelManager manager; ASSERT_TRUE(RegisterTestModel( - manager, "my_embed_v1", + manager, "my_embed", std::make_shared(128, 4), "test-v1")); - ASSERT_TRUE(RegisterTestModel(manager, "my_rerank_v1", + ASSERT_TRUE(RegisterTestModel(manager, "my_rerank", std::make_shared(4), "test-v1")); - EXPECT_TRUE(manager.HasModel("my_embed_v1")); - EXPECT_TRUE(manager.HasModel("my_rerank_v1")); + EXPECT_TRUE(manager.HasModel("my_embed")); + EXPECT_TRUE(manager.HasModel("my_rerank")); EXPECT_FALSE(manager.HasModel("unknown_model")); - auto retrieved = manager.GetModel("my_rerank_v1"); + auto retrieved = manager.GetModel("my_rerank"); ASSERT_NE(retrieved, nullptr); EXPECT_EQ(retrieved->Capability(), "rerank"); - EXPECT_EQ(manager.GetModel("my_rerank_v1"), nullptr); + EXPECT_EQ(manager.GetModel("my_rerank"), nullptr); } TEST(SessionContextTest, TypedDynamicResourcesRejectMismatchedAccess) { diff --git a/tests/unit/core/test_node_ownership_and_reuse.cpp b/tests/unit/core/test_node_ownership_and_reuse.cpp index 500e57e8..c7ec7e1e 100644 --- a/tests/unit/core/test_node_ownership_and_reuse.cpp +++ b/tests/unit/core/test_node_ownership_and_reuse.cpp @@ -120,13 +120,13 @@ class DistinctMockEmbeddingModel : public IEmbeddingModel { TEST(NodeOwnershipAndReuseTest, CommonEmbeddingAndVectorTopKExecution) { SessionContext session_ctx; - RegisterTestModel(session_ctx.GetModelManager(), "embed_model_v2", + RegisterTestModel(session_ctx.GetModelManager(), "embed_model", std::make_shared(), "test-v1"); auto embed_node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(embed_node, nullptr); - nlohmann::json node_cfg = {{"bind_model", "embed_model_v2"}, + nlohmann::json node_cfg = {{"bind_model", "embed_model"}, {"normalize", true}}; ASSERT_TRUE(InitNodeForTest(*embed_node, node_cfg, &session_ctx)); diff --git a/tests/unit/core/test_validated_pipeline_plan.cpp b/tests/unit/core/test_validated_pipeline_plan.cpp index 0933239b..25759286 100644 --- a/tests/unit/core/test_validated_pipeline_plan.cpp +++ b/tests/unit/core/test_validated_pipeline_plan.cpp @@ -732,7 +732,7 @@ TEST_F(ValidatedPipelinePlanTest, nlohmann::json pipeline_json = { {"biz_name", "doc_qa"}, {"models", - nlohmann::json::array({{{"model_id", "embed_model_v1"}, + nlohmann::json::array({{{"model_id", "embed_model"}, {"model_type", "test_biz_embedding"}, {"backend", "test_tensor_backend"}, {"model_path", "fixture.bin"}, @@ -745,7 +745,7 @@ TEST_F(ValidatedPipelinePlanTest, {"depends_on", nlohmann::json::array()}, {"inputs", {{"text", "raw_queries"}}}, {"config", - {{"bind_model", "embed_model_v1"}, {"lifetime", "session"}}}}})}}; + {{"bind_model", "embed_model"}, {"lifetime", "session"}}}}})}}; auto plan = PipelineValidator::ValidateAndPlan(pipeline_json); auto diagnostic = diff --git a/tests/unit/nodes/test_asr_transcribe_node.cpp b/tests/unit/nodes/test_asr_transcribe_node.cpp index 6a512847..c45c20f9 100644 --- a/tests/unit/nodes/test_asr_transcribe_node.cpp +++ b/tests/unit/nodes/test_asr_transcribe_node.cpp @@ -22,9 +22,9 @@ class AsrTranscribeNodeTest : public ::testing::Test { session_ctx_ = std::make_unique(); asr_model_ = std::make_shared(); - ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "asr_model_v1", asr_model_, - "test-revision", "test_asr_model", "asr", "test_tensor_backend")); + ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), "asr_model", + asr_model_, "test-revision", "test_asr_model", + "asr", "test_tensor_backend")); } std::unique_ptr session_ctx_; std::shared_ptr asr_model_; @@ -34,7 +34,7 @@ TEST_F(AsrTranscribeNodeTest, ProcessAudioTranscription) { auto node = NodeRegistry::Instance().Create("AsrTranscribeNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "asr_model_v1"}}; + nlohmann::json cfg = {{"bind_model", "asr_model"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -56,7 +56,7 @@ TEST_F(AsrTranscribeNodeTest, ProcessAudioTranscription) { TEST_F(AsrTranscribeNodeTest, EmptyAudioInput) { auto node = NodeRegistry::Instance().Create("AsrTranscribeNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model"}}, session_ctx_.get())); AlgContext ctx; @@ -71,7 +71,7 @@ TEST_F(AsrTranscribeNodeTest, EmptyAudioInput) { TEST_F(AsrTranscribeNodeTest, MissingInputFailsClosed) { auto node = NodeRegistry::Instance().Create("AsrTranscribeNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model"}}, session_ctx_.get())); AlgContext empty_ctx; @@ -81,7 +81,7 @@ TEST_F(AsrTranscribeNodeTest, MissingInputFailsClosed) { TEST_F(AsrTranscribeNodeTest, InvalidModelOutputFailsClosed) { auto node = NodeRegistry::Instance().Create("AsrTranscribeNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "asr_model"}}, session_ctx_.get())); AudioPcmBatch audio; diff --git a/tests/unit/nodes/test_common_nodes.cpp b/tests/unit/nodes/test_common_nodes.cpp index daf89bf1..3403a68d 100644 --- a/tests/unit/nodes/test_common_nodes.cpp +++ b/tests/unit/nodes/test_common_nodes.cpp @@ -35,25 +35,25 @@ class CommonNodesTest : public ::testing::Test { session_ctx_->SetRuntimeOptions(options); ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "embed_model_v1", + session_ctx_->GetModelManager(), "embed_model", std::make_shared(384, 4), "test-v1")); ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "rerank_model_v1", + session_ctx_->GetModelManager(), "rerank_model", std::make_shared(4), "test-v1")); - ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "llm_model_v1", - std::make_shared(2), "test-v1")); + ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), "llm_model", + std::make_shared(2), + "test-v1")); auto asr_model = std::make_shared(); ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "asr_model_v1", std::move(asr_model), + session_ctx_->GetModelManager(), "asr_model", std::move(asr_model), "test-revision", "test_asr_model", "asr", "test_tensor_backend")); auto ocr_model = std::make_shared(); ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "ocr_model_v1", std::move(ocr_model), + session_ctx_->GetModelManager(), "ocr_model", std::move(ocr_model), "test-revision", "test_ocr_model", "ocr", "test_tensor_backend")); } @@ -314,7 +314,7 @@ TEST_F(CommonNodesTest, TextEmbeddingNodeComprehensive) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "embed_model_v1"}, + nlohmann::json cfg = {{"bind_model", "embed_model"}, {"normalize", true}, {"lifetime", "session"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); @@ -383,7 +383,7 @@ TEST_F(CommonNodesTest, TextRerankNodeComprehensive) { auto node = NodeRegistry::Instance().Create("TextRerankNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "rerank_model_v1"}, {"top_k", 1}}; + nlohmann::json cfg = {{"bind_model", "rerank_model"}, {"top_k", 1}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get(), nullptr, {"pairs", "candidate_texts"})); @@ -428,14 +428,14 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, {"node_type", "TextRerankNode"}, {"inputs", {{"pairs", "any_pairs"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan_pairs = PipelineValidator::ValidateAndPlan(valid_pipeline_pairs); EXPECT_TRUE(plan_pairs.report.ok); @@ -445,7 +445,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, @@ -453,7 +453,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"inputs", {{"queries", "any_queries"}, {"candidates", "any_candidates"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan_qc = PipelineValidator::ValidateAndPlan(valid_pipeline_qc); EXPECT_TRUE(plan_qc.report.ok); @@ -463,7 +463,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, @@ -472,7 +472,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {{"queries", "any_queries"}, {"candidate_texts", "any_candidate_texts"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan_qct = PipelineValidator::ValidateAndPlan(valid_pipeline_qct); EXPECT_TRUE(plan_qct.report.ok); @@ -482,14 +482,14 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, {"node_type", "TextRerankNode"}, {"inputs", {{"candidates", "some_cand"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan1 = PipelineValidator::ValidateAndPlan(bad_pipeline_1); EXPECT_FALSE(plan1.report.ok); EXPECT_TRUE(has_constraint_err(plan1.report)); @@ -500,14 +500,14 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, {"node_type", "TextRerankNode"}, {"inputs", {{"queries", "some_queries"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan2 = PipelineValidator::ValidateAndPlan(bad_pipeline_2); EXPECT_FALSE(plan2.report.ok); EXPECT_TRUE(has_constraint_err(plan2.report)); @@ -518,14 +518,14 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, {"node_type", "TextRerankNode"}, {"inputs", {{"pairs", "any_pairs"}, {"candidates", "any_candidates"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan3 = PipelineValidator::ValidateAndPlan(bad_pipeline_3); EXPECT_FALSE(plan3.report.ok); EXPECT_TRUE(has_constraint_err(plan3.report)); @@ -536,7 +536,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, @@ -546,7 +546,7 @@ TEST_F(CommonNodesTest, TextRerankCombinationConstraintsValidation) { {"candidates", "any_candidates"}, {"candidate_texts", "any_candidate_texts"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan4 = PipelineValidator::ValidateAndPlan(bad_pipeline_4); EXPECT_FALSE(plan4.report.ok); EXPECT_TRUE(has_constraint_err(plan4.report)); @@ -648,7 +648,7 @@ TEST_F(CommonNodesTest, LlmGenerateNodeComprehensive) { ASSERT_NE(node, nullptr); nlohmann::json cfg = { - {"bind_model", "llm_model_v1"}, {"temperature", 0.5}, {"max_tokens", 64}}; + {"bind_model", "llm_model"}, {"temperature", 0.5}, {"max_tokens", 64}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -668,7 +668,7 @@ TEST_F(CommonNodesTest, AsrTranscribeNodeComprehensive) { auto node = NodeRegistry::Instance().Create("AsrTranscribeNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "asr_model_v1"}}; + nlohmann::json cfg = {{"bind_model", "asr_model"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -689,7 +689,7 @@ TEST_F(CommonNodesTest, OcrDetectNodeComprehensive) { auto node = NodeRegistry::Instance().Create("OcrDetectNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "ocr_model_v1"}}; + nlohmann::json cfg = {{"bind_model", "ocr_model"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -1318,7 +1318,7 @@ TEST_F(CommonNodesTest, PromptConfigurationRejectedByValidatorAndInit) { doc["pipeline"][0]["config"] = config; const auto preflight = PipelineValidator::ValidateAndPlan(doc); EXPECT_FALSE(preflight.report.ok); - config["bind_model"] = "llm_model_v1"; + config["bind_model"] = "llm_model"; auto node = NodeRegistry::Instance().Create("PromptGuidedLlmNode"); ASSERT_NE(node, nullptr); std::string init_error; @@ -1412,16 +1412,16 @@ TEST_F(CommonNodesTest, StarterTextFunctionsFollowTheDocumentedExercise) { TEST_F(CommonNodesTest, GeneratedCapabilityTemplatesCompileBindAndExecute) { CheckScaffoldExecution("ScaffoldComputeNode", "", session_ctx_.get()); - CheckScaffoldExecution( - "ScaffoldModelLlmNode", "llm_model_v1", session_ctx_.get()); + CheckScaffoldExecution("ScaffoldModelLlmNode", + "llm_model", session_ctx_.get()); CheckScaffoldExecution( - "ScaffoldModelEmbeddingNode", "embed_model_v1", session_ctx_.get()); + "ScaffoldModelEmbeddingNode", "embed_model", session_ctx_.get()); CheckScaffoldExecution( - "ScaffoldModelAsrNode", "asr_model_v1", session_ctx_.get()); + "ScaffoldModelAsrNode", "asr_model", session_ctx_.get()); CheckScaffoldExecution( - "ScaffoldModelRerankNode", "rerank_model_v1", session_ctx_.get()); + "ScaffoldModelRerankNode", "rerank_model", session_ctx_.get()); CheckScaffoldExecution( - "ScaffoldModelOcrNode", "ocr_model_v1", session_ctx_.get()); + "ScaffoldModelOcrNode", "ocr_model", session_ctx_.get()); auto node = NodeRegistry::Instance().Create("ScaffoldConversionNode"); ASSERT_TRUE( InitNodeForTest(*node, nlohmann::json::object(), session_ctx_.get())); diff --git a/tests/unit/nodes/test_llm_generate_node.cpp b/tests/unit/nodes/test_llm_generate_node.cpp index 0cc22365..b8971176 100644 --- a/tests/unit/nodes/test_llm_generate_node.cpp +++ b/tests/unit/nodes/test_llm_generate_node.cpp @@ -79,8 +79,8 @@ class LlmGenerateNodeTest : public ::testing::Test { session_ctx_ = std::make_unique(); model_ = std::make_shared(); - ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), - "llm_model_v1", model_, "test-v1")); + ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), "llm_model", + model_, "test-v1")); } std::unique_ptr session_ctx_; std::shared_ptr model_; @@ -90,7 +90,7 @@ TEST_F(LlmGenerateNodeTest, ModelReasonReachesRequestErrorWithoutOutput) { model_->fail_with_input_reason = true; auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model"}}, session_ctx_.get())); AlgContext ctx; ctx.Publish("prompt", TextBatch{{0, 0, "backend context capacity exceeded"}}); @@ -127,7 +127,7 @@ TEST_F(LlmGenerateNodeTest, ProcessBatchPromptInference) { auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "llm_model_v1"}, + nlohmann::json cfg = {{"bind_model", "llm_model"}, {"temperature", 0.7}, {"max_tokens", 128}, {"top_k", 32}, @@ -159,14 +159,13 @@ TEST_F(LlmGenerateNodeTest, ProcessBatchPromptInference) { TEST_F(LlmGenerateNodeTest, RejectsInvalidUnifiedGenerationOptions) { auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - EXPECT_FALSE(InitNodeForTest(*node, - {{"bind_model", "llm_model_v1"}, {"top_k", -1}}, - session_ctx_.get())); + EXPECT_FALSE(InitNodeForTest( + *node, {{"bind_model", "llm_model"}, {"top_k", -1}}, session_ctx_.get())); node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); EXPECT_FALSE(InitNodeForTest( - *node, {{"bind_model", "llm_model_v1"}, {"repetition_penalty", 0.0}}, + *node, {{"bind_model", "llm_model"}, {"repetition_penalty", 0.0}}, session_ctx_.get())); } @@ -183,7 +182,7 @@ TEST_F(LlmGenerateNodeTest, ValidatorAndInitializationRejectInvalidOptions) { // 检查 Node 的预检诊断无需构造 Model。 for (auto config : invalid) { SCOPED_TRACE(config.dump()); - config["bind_model"] = "llm_model_v1"; + config["bind_model"] = "llm_model"; const nlohmann::json pipeline = { {"biz_name", "entity_extract"}, {"pipeline", nlohmann::json::array({{{"id", "generate"}, @@ -205,7 +204,7 @@ TEST_F(LlmGenerateNodeTest, ValidatorAndInitializationRejectInvalidOptions) { std::string diagnostic; EXPECT_NE( PrepareNodePlanForTest("LlmGenerateNode", - {{"bind_model", "llm_model_v1"}, + {{"bind_model", "llm_model"}, {"stop_words", nlohmann::json::array({"END"})}, {"max_tokens", 32768}, {"top_p", 1.0e-9}}, @@ -217,7 +216,7 @@ TEST_F(LlmGenerateNodeTest, ValidatorAndInitializationRejectInvalidOptions) { TEST_F(LlmGenerateNodeTest, MissingInputFailsClosed) { auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model"}}, session_ctx_.get())); AlgContext empty_ctx; @@ -227,7 +226,7 @@ TEST_F(LlmGenerateNodeTest, MissingInputFailsClosed) { TEST_F(LlmGenerateNodeTest, EmptyBatchSkipsInference) { auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model"}}, session_ctx_.get())); AlgContext ctx; @@ -242,7 +241,7 @@ TEST_F(LlmGenerateNodeTest, EmptyBatchSkipsInference) { TEST_F(LlmGenerateNodeTest, InvalidModelOutputFailsClosed) { auto node = NodeRegistry::Instance().Create("LlmGenerateNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "llm_model"}}, session_ctx_.get())); TextBatch prompts = {{3, 0, "first"}, {3, 1, "second"}}; diff --git a/tests/unit/nodes/test_ocr_detect_node.cpp b/tests/unit/nodes/test_ocr_detect_node.cpp index 7dcedeee..1a57f9cc 100644 --- a/tests/unit/nodes/test_ocr_detect_node.cpp +++ b/tests/unit/nodes/test_ocr_detect_node.cpp @@ -22,9 +22,9 @@ class OcrDetectNodeTest : public ::testing::Test { session_ctx_ = std::make_unique(); ocr_model_ = std::make_shared(); - ASSERT_TRUE(RegisterTestModel( - session_ctx_->GetModelManager(), "ocr_model_v1", ocr_model_, - "test-revision", "test_ocr_model", "ocr", "test_tensor_backend")); + ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), "ocr_model", + ocr_model_, "test-revision", "test_ocr_model", + "ocr", "test_tensor_backend")); } std::unique_ptr session_ctx_; std::shared_ptr ocr_model_; @@ -34,7 +34,7 @@ TEST_F(OcrDetectNodeTest, ProcessOcrDetection) { auto node = NodeRegistry::Instance().Create("OcrDetectNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "ocr_model_v1"}}; + nlohmann::json cfg = {{"bind_model", "ocr_model"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -56,7 +56,7 @@ TEST_F(OcrDetectNodeTest, ProcessOcrDetection) { TEST_F(OcrDetectNodeTest, MissingInputFailsClosed) { auto node = NodeRegistry::Instance().Create("OcrDetectNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "ocr_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "ocr_model"}}, session_ctx_.get())); AlgContext empty_ctx; @@ -66,7 +66,7 @@ TEST_F(OcrDetectNodeTest, MissingInputFailsClosed) { TEST_F(OcrDetectNodeTest, InvalidModelOutputFailsClosed) { auto node = NodeRegistry::Instance().Create("OcrDetectNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "ocr_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "ocr_model"}}, session_ctx_.get())); ImageRefBatch images; diff --git a/tests/unit/nodes/test_text_embedding_node.cpp b/tests/unit/nodes/test_text_embedding_node.cpp index 64f9e7b9..abb7df4d 100644 --- a/tests/unit/nodes/test_text_embedding_node.cpp +++ b/tests/unit/nodes/test_text_embedding_node.cpp @@ -72,7 +72,7 @@ class TextEmbeddingNodeTest : public ::testing::Test { session_ctx_ = std::make_unique(); counting_model_ = std::make_shared(); ASSERT_TRUE(RegisterTestModel(session_ctx_->GetModelManager(), - "embed_model_v1", counting_model_, + "embed_model", counting_model_, "revision-1")); } std::unique_ptr session_ctx_; @@ -83,7 +83,7 @@ TEST_F(TextEmbeddingNodeTest, ProcessRequestLifetime) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "embed_model_v1"}, {"normalize", true}}; + nlohmann::json cfg = {{"bind_model", "embed_model"}, {"normalize", true}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); AlgContext ctx; @@ -103,7 +103,7 @@ TEST_F(TextEmbeddingNodeTest, SessionCachingSingleFlightAndInvalidation) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - nlohmann::json cfg = {{"bind_model", "embed_model_v1"}, + nlohmann::json cfg = {{"bind_model", "embed_model"}, {"normalize", true}, {"lifetime", "session"}}; EXPECT_TRUE(InitNodeForTest(*node, cfg, session_ctx_.get())); @@ -151,7 +151,7 @@ TEST_F(TextEmbeddingNodeTest, SessionCachingSingleFlightAndInvalidation) { // 模型热更新会改变 revision,使其余完全相同的会话缓存条目失效。 ASSERT_TRUE(session_ctx_->GetModelManager().UpdateModelRevision( - "embed_model_v1", "revision-2")); + "embed_model", "revision-2")); { AlgContext ctx; TextBatch original_corpus; @@ -166,7 +166,7 @@ TEST_F(TextEmbeddingNodeTest, SessionCachingSingleFlightAndInvalidation) { TEST_F(TextEmbeddingNodeTest, MissingInputFailsClosed) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model"}}, session_ctx_.get())); AlgContext empty_ctx; @@ -176,7 +176,7 @@ TEST_F(TextEmbeddingNodeTest, MissingInputFailsClosed) { TEST_F(TextEmbeddingNodeTest, EmptyBatchSkipsInference) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model"}}, session_ctx_.get())); AlgContext ctx; @@ -191,7 +191,7 @@ TEST_F(TextEmbeddingNodeTest, EmptyBatchSkipsInference) { TEST_F(TextEmbeddingNodeTest, InvalidRequestOutputFailsClosed) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); - ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model_v1"}}, + ASSERT_TRUE(InitNodeForTest(*node, {{"bind_model", "embed_model"}}, session_ctx_.get())); TextBatch inputs = {{8, 0, "first"}, {8, 1, "second"}}; @@ -216,7 +216,7 @@ TEST_F(TextEmbeddingNodeTest, InvalidSessionOutputIsNotCached) { auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); ASSERT_TRUE(InitNodeForTest( - *node, {{"bind_model", "embed_model_v1"}, {"lifetime", "session"}}, + *node, {{"bind_model", "embed_model"}, {"lifetime", "session"}}, session_ctx_.get())); TextBatch inputs = {{9, 0, "static first"}, {9, 1, "static second"}}; @@ -243,7 +243,7 @@ TEST_F(TextEmbeddingNodeTest, auto node = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_NE(node, nullptr); ASSERT_TRUE(InitNodeForTest( - *node, {{"bind_model", "embed_model_v1"}, {"lifetime", lifetime}}, + *node, {{"bind_model", "embed_model"}, {"lifetime", lifetime}}, session_ctx_.get())); counting_model_->fail_inference = true; const int calls_before = counting_model_->infer_calls.load(); @@ -306,10 +306,10 @@ TEST_F(TextEmbeddingNodeTest, SessionCacheCollisionReproductionDefeated) { ASSERT_NE(node_a, nullptr); ASSERT_NE(node_b, nullptr); ASSERT_TRUE(InitNodeForTest( - *node_a, {{"bind_model", "embed_model_v1"}, {"lifetime", "session"}}, + *node_a, {{"bind_model", "embed_model"}, {"lifetime", "session"}}, session_ctx_.get())); ASSERT_TRUE(InitNodeForTest( - *node_b, {{"bind_model", "embed_model_v1"}, {"lifetime", "session"}}, + *node_b, {{"bind_model", "embed_model"}, {"lifetime", "session"}}, session_ctx_.get())); // 语料 A:["a", "b\0\1c"] @@ -379,7 +379,7 @@ TEST_F(TextEmbeddingNodeTest, SessionCacheCollisionReproductionDefeated) { // 不同的 normalize 选项产生不同的缓存条目 auto node_no_norm = NodeRegistry::Instance().Create("TextEmbeddingNode"); ASSERT_TRUE(InitNodeForTest(*node_no_norm, - {{"bind_model", "embed_model_v1"}, + {{"bind_model", "embed_model"}, {"lifetime", "session"}, {"normalize", false}}, session_ctx_.get())); @@ -405,7 +405,7 @@ TEST_F(TextEmbeddingNodeTest, StrictPlanKeepsDistinctCorpusCacheIdentities) { "biz_name": "keyword_match", "models": [ { - "model_id": "embed_model_v1", + "model_id": "embed_model", "model_type": "bge_embedding", "backend": "onnxruntime", "model_path": "demo/fixtures/mock/artifacts/neutral-embedding.fixture", @@ -435,7 +435,7 @@ TEST_F(TextEmbeddingNodeTest, StrictPlanKeepsDistinctCorpusCacheIdentities) { "id": "embed_a", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1", + "bind_model": "embed_model", "lifetime": "session" }, "inputs": { @@ -464,7 +464,7 @@ TEST_F(TextEmbeddingNodeTest, StrictPlanKeepsDistinctCorpusCacheIdentities) { "id": "embed_b", "node_type": "TextEmbeddingNode", "config": { - "bind_model": "embed_model_v1", + "bind_model": "embed_model", "lifetime": "session" }, "inputs": { diff --git a/tests/unit/nodes/test_text_rerank_node.cpp b/tests/unit/nodes/test_text_rerank_node.cpp index 5c25cfea..4364d253 100644 --- a/tests/unit/nodes/test_text_rerank_node.cpp +++ b/tests/unit/nodes/test_text_rerank_node.cpp @@ -348,14 +348,14 @@ TEST_F(TextRerankNodeTest, PortConstraintsValidation) { {"models", {{{"model_type", "test_biz_rerank"}, {"backend", "test_tensor_backend"}, - {"model_id", "rerank_model_v1"}, + {"model_id", "rerank_model"}, {"model_path", "./models/rerank.bin"}}}}, {"pipeline", {{{"id", "node_0_TextRerankNode"}, {"node_type", "TextRerankNode"}, {"inputs", {{"candidates", "doc_candidates"}}}, {"outputs", {{"ranked", "ranked_results"}}}, - {"config", {{"bind_model", "rerank_model_v1"}}}}}}}; + {"config", {{"bind_model", "rerank_model"}}}}}}}; auto plan = PipelineValidator::ValidateAndPlan(bad_pipeline); EXPECT_FALSE(plan.report.ok); diff --git a/tools/pipeline_studio/server.py b/tools/pipeline_studio/server.py index a5a0a6d0..bbc12636 100755 --- a/tools/pipeline_studio/server.py +++ b/tools/pipeline_studio/server.py @@ -186,7 +186,7 @@ def get_tool_fingerprint() -> str: class WorkbenchService: - """/api/v1 背后的状态与文件系统边界。""" + """/api 背后的状态与文件系统边界。""" def __init__(self, config_root: Path = CONFIG_ROOT, initial: Path | None = None): self.config_root = config_root.resolve() @@ -1074,34 +1074,34 @@ def _dispatch(self, method: str) -> None: body: dict[str, Any] = {} if method in ("POST", "PUT", "DELETE"): body = self._body() - if method == "GET" and path == "/api/v1/catalog": + if method == "GET" and path == "/api/catalog": payload = service.catalog(query.get("io_binding", [""])[0]) - elif method == "GET" and path == "/api/v1/assets": + elif method == "GET" and path == "/api/assets": payload = service.assets() - elif method == "POST" and path == "/api/v1/selection": + elif method == "POST" and path == "/api/selection": payload = service.verify_selection(body.get("pipeline"), body.get("variant", ""), body.get("model_root", "models")) - elif method == "GET" and path == "/api/v1/profiles": + elif method == "GET" and path == "/api/profiles": payload = service.profiles() - elif method == "GET" and path == "/api/v1/pipelines": + elif method == "GET" and path == "/api/pipelines": payload = service.pipelines() - elif method == "GET" and path == "/api/v1/pipeline": + elif method == "GET" and path == "/api/pipeline": payload = service.open_pipeline(query.get("filename", [""])[0]) - elif method == "GET" and path == "/api/v1/initial": + elif method == "GET" and path == "/api/initial": payload = service.initial_pipeline() - elif method == "GET" and path.startswith("/api/v1/runs/"): + elif method == "GET" and path.startswith("/api/runs/"): payload = service.run_status(path.rsplit("/", 1)[-1]) - elif method == "POST" and path == "/api/v1/validate": + elif method == "POST" and path == "/api/validate": payload = service.validate( body.get("pipeline"), body.get("explain", False) ) - elif method == "POST" and path == "/api/v1/fixes/preview": + elif method == "POST" and path == "/api/fixes/preview": payload = service.preview_fix( body.get("pipeline"), body.get("patch", []), body.get("revision") or body.get("expected_revision"), body.get("tool_fingerprint"), ) - elif method == "POST" and path == "/api/v1/authoring/preview": + elif method == "POST" and path == "/api/authoring/preview": allowed = {"pipeline", "operation", "operations", "require_valid", "revision", "expected_revision", "tool_fingerprint"} unknown = set(body) - allowed @@ -1118,7 +1118,7 @@ def _dispatch(self, method: str) -> None: expected_revision=body.get("revision") or body.get("expected_revision"), tool_fingerprint=body.get("tool_fingerprint"), ) - elif method == "POST" and path == "/api/v1/preflight": + elif method == "POST" and path == "/api/preflight": payload = service.preflight( body.get("pipeline"), profile_name=body.get("profile", ""), @@ -1126,27 +1126,27 @@ def _dispatch(self, method: str) -> None: model_root=body.get("model_root", "models"), filename=body.get("filename", ""), ) - elif method == "POST" and path == "/api/v1/deployment/associate": + elif method == "POST" and path == "/api/deployment/associate": payload = service.associate_deployment( body.get("pipeline_name", "") or body.get("filename", ""), body.get("conf_name", "") or body.get("conf_path", ""), model_root=body.get("model_root", "models"), profile_name=body.get("profile", ""), ) - elif method == "POST" and path == "/api/v1/init": + elif method == "POST" and path == "/api/init": payload = service.init_pipeline( body.get("io_binding", ""), body.get("profile", ""), body.get("empty", False) ) - elif method == "POST" and path == "/api/v1/pipelines": + elif method == "POST" and path == "/api/pipelines": payload = service.save_pipeline( body.get("filename", ""), body.get("pipeline"), None, save_as=True ) - elif method == "POST" and path == "/api/v1/solutions": + elif method == "POST" and path == "/api/solutions": payload = service.save_solution( body.get("filename", ""), body.get("pipeline"), body.get("profile", ""), body.get("model_root", "models") ) - elif method == "PUT" and path == "/api/v1/pipeline": + elif method == "PUT" and path == "/api/pipeline": payload = service.save_pipeline( body.get("filename", ""), body.get("pipeline"), @@ -1155,12 +1155,12 @@ def _dispatch(self, method: str) -> None: profile_name=body.get("profile", ""), model_root=body.get("model_root", "models"), ) - elif method == "POST" and path == "/api/v1/runs": + elif method == "POST" and path == "/api/runs": payload = service.start_run( body.get("pipeline"), body.get("profile", ""), body.get("model_root", "models"), filename=body.get("filename", ""), conf_name=body.get("conf_name", ""), ) - elif method == "DELETE" and path.startswith("/api/v1/runs/"): + elif method == "DELETE" and path.startswith("/api/runs/"): payload = service.cancel_run(path.rsplit("/", 1)[-1]) else: raise StudioError("NOT_FOUND", path, 404) diff --git a/tools/pipeline_studio/web/api.js b/tools/pipeline_studio/web/api.js index e326e3d0..7b246aab 100644 --- a/tools/pipeline_studio/web/api.js +++ b/tools/pipeline_studio/web/api.js @@ -5,7 +5,7 @@ export async function api(path, options = {}) { const { allowFalse = false, ...fetchOptions } = options; const headers = { "Content-Type": "application/json", ...(options.headers || {}) }; // 通过端口代理打开 Studio 时,保留页面的目录前缀。 - const url = new URL(`api/v1${path}`, location.href); + const url = new URL(`api${path}`, location.href); let response; try { response = await fetch(url, { ...fetchOptions, headers }); } catch (error) {