From 1861d448eb697c896062132d9961a07867c77f0b Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 15:35:10 +0800 Subject: [PATCH 1/3] refactor(adapter): use the biz key matched_policy in the audit converter The audit output converter declared a plural logical port that existed only to be renamed back to the biz egress key through the binding. Use the biz key directly, drop kMatchedPolicies and the binding port mapping, and assert that the converter ports match the biz egress names. Pipeline configs and the external contract are unchanged. Co-Authored-By: Claude Opus 5.5 --- doc/CHANGELOG.md | 3 +++ doc/dev_guide/business_onboarding.md | 6 ++--- include/adapter/biz_blackboard_keys.h | 3 --- src/adapter/biz/dialogue_audit_bindings.cpp | 1 - src/adapter/output/audit_result_output.cpp | 24 +++++++++---------- tests/unit/adapter/test_adapter_purity.cpp | 12 +++++----- .../unit/adapter/test_complex_converters.cpp | 24 +++++++++---------- 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index ccde4a20..2c1d401b 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +对话合规审核的输出 Converter 直接使用业务出口键 `matched_policy`,删除只为改名存在的 +`kMatchedPolicies`(`matched_policies`)及其 Binding 端口映射;Pipeline 配置与外部契约不变。 + Converter 定义精简(不涉及 Operator ABI、Pipeline JSON 与 `.conf`):删除没有运行时作用的 `schema_version`、`external_type`、输出 `cardinality` 与 `capacity_policy`,以及槽位的 `value_type` 与 `capacity_fields`。输出槽容量字段只由 ValueType 决定,`ExternalInputSlot` / `ExternalOutputSlot` diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 74c245a1..5c6e1f41 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -145,9 +145,9 @@ Operator 的宿主输入校验会拒绝 `CompanyString` 中的原始嵌入 NUL 共享端口用 `MakeBlackboardKey(name)` 定义一次;转换器 Definition 使用 `RequiredInputPort(port)` / `OutputPort(port)`,回调通过 `bindings.Key(port)` -读取或发布。同名端口在绑定中无需声明;非同名映射使用 -`BindIoPort(logical_port, actual_key)`,两端的 C++ 类型必须一致。例如审核输出使用 -`BindIoPort(kMatchedPolicies, kMatchedPolicy)`,不能直接绕过绑定读写实际 key。 +读取或发布,不能绕过绑定直接读写实际 key。转换器端口直接使用业务出入口的键名,绑定中无需声明; +不要为命名差异引入映射。确需非同名映射时使用 `BindIoPort(logical_port, actual_key)`, +两端的 C++ 类型必须一致。 需要完整映射的代码调用 `EffectivePortMapping`,不要直接读取绑定的端口表。 外部必需槽的常见写法是 `ExternalInputSlot(slot)` 和 diff --git a/include/adapter/biz_blackboard_keys.h b/include/adapter/biz_blackboard_keys.h index 8624e918..e7bc42bc 100644 --- a/include/adapter/biz_blackboard_keys.h +++ b/include/adapter/biz_blackboard_keys.h @@ -30,9 +30,6 @@ inline constexpr auto kStructuredVerdicts = MakeBlackboardKey("structured_verdicts"); inline constexpr auto kMatchedPolicy = MakeBlackboardKey("matched_policy"); -// 输出 Converter 的逻辑端口名与业务 Blackboard 键不同。 -inline constexpr auto kMatchedPolicies = - MakeBlackboardKey("matched_policies"); inline constexpr auto kImagePaths = MakeBlackboardKey("image_paths"); inline constexpr auto kUserQueries = diff --git a/src/adapter/biz/dialogue_audit_bindings.cpp b/src/adapter/biz/dialogue_audit_bindings.cpp index cef0744a..f2de8785 100644 --- a/src/adapter/biz/dialogue_audit_bindings.cpp +++ b/src/adapter/biz/dialogue_audit_bindings.cpp @@ -34,7 +34,6 @@ IoBindingDefinition MakeDialogueAuditOperatorBinding() { def.input_converter_id = "audit.plain.operator.v1"; def.output_converter_id = "audit_result.plain.operator.v1"; - def.output_ports = {BindIoPort(kMatchedPolicies, kMatchedPolicy)}; return def; } diff --git a/src/adapter/output/audit_result_output.cpp b/src/adapter/output/audit_result_output.cpp index da405425..70f86005 100644 --- a/src/adapter/output/audit_result_output.cpp +++ b/src/adapter/output/audit_result_output.cpp @@ -33,9 +33,9 @@ int EncodeOperatorAuditResult(AlgContext* context, *context, bindings, kStructuredVerdicts, options, status, "verdicts"); if (!verdicts) return COMPANY_ALG_ERR_INVALID_INPUT; - const auto* matched_policies = - ReadOutputValue(*context, bindings, kMatchedPolicies, options, status); - if (!matched_policies) return COMPANY_ALG_ERR_INVALID_INPUT; + const auto* matched_policy = + ReadOutputValue(*context, bindings, kMatchedPolicy, options, status); + if (!matched_policy) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); if (!raw_req_ids) return COMPANY_ALG_ERR_INVALID_INPUT; @@ -47,10 +47,10 @@ int EncodeOperatorAuditResult(AlgContext* context, "destination", options.converter_id.c_str()); } - if (matched_policies->size() < count) { + if (matched_policy->size() < count) { return AdapterValidationHelper::ReturnInvalidInput( - status, "matched_policies count mismatch in AlgContext", - "matched_policies", options.converter_id.c_str()); + status, "matched_policy count mismatch in AlgContext", "matched_policy", + options.converter_id.c_str()); } std::vector verdicts_by_request; @@ -59,9 +59,9 @@ int EncodeOperatorAuditResult(AlgContext* context, return COMPANY_ALG_ERR_INVALID_INPUT; } - std::vector matched_policies_by_request; - if (!IndexResults(matched_policies, raw_req_ids, &matched_policies_by_request, - "matched_policies", options.converter_id.c_str(), status, + std::vector matched_policy_by_request; + if (!IndexResults(matched_policy, raw_req_ids, &matched_policy_by_request, + "matched_policy", options.converter_id.c_str(), status, true)) { return COMPANY_ALG_ERR_INVALID_INPUT; } @@ -78,7 +78,7 @@ int EncodeOperatorAuditResult(AlgContext* context, out->request_id = (*raw_req_ids)[i]; const auto& verdict_item = verdicts_by_request[i]->data; - if (matched_policies_by_request[i]->data.rank != 1 || + if (matched_policy_by_request[i]->data.rank != 1 || !IsSuccessfulDocument(verdict_item) || !verdict_item.structured_data.contains("risk_level") || !verdict_item.structured_data.contains("risk_score") || @@ -103,7 +103,7 @@ int EncodeOperatorAuditResult(AlgContext* context, } const std::string& verdict_json = verdict_item.json_payload; - std::string policy_clause = matched_policies_by_request[i]->data.text; + std::string policy_clause = matched_policy_by_request[i]->data.text; out->risk_score = risk_score; out->status_code = 0; @@ -139,7 +139,7 @@ OutputConverterDefinition MakeOperatorAuditResultOutputConverter() { def.external_slots = { ExternalOutputSlot(kOutputSlot)}; def.logical_ports = {RequiredInputPort(kStructuredVerdicts), - RequiredInputPort(kMatchedPolicies, "N:1")}; + RequiredInputPort(kMatchedPolicy, "N:1")}; def.encode_fn = &EncodeOperatorAuditResult; return def; } diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index eeeeddac..224a5a65 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -277,7 +277,7 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapterPurity) { InputPortBindings( {{"user_texts", "user_texts"}, {"channel_names", "channel_names"}}), OutputPortBindings({{"structured_verdicts", "structured_verdicts"}, - {"matched_policies", "matched_policies"}})); + {"matched_policy", "matched_policy"}})); std::string text_str = "audit sentence"; std::string chan_str = "channel_vip"; @@ -296,7 +296,7 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapterPurity) { RankedTextBatch policies; policies.emplace_back(0, 0, RankedCandidate("Clause 1", 1.0f, 1, 0)); - harness.Publish("matched_policies", std::move(policies)); + harness.Publish("matched_policy", std::move(policies)); AuditOutputFixture audit_fix; std::vector outputs = {audit_fix.out}; @@ -593,7 +593,7 @@ TEST_F(AdapterPurityTest, OutputPortBindings out_bindings( {{"structured_verdicts", "structured_verdicts"}, - {"matched_policies", "matched_policies"}}); + {"matched_policy", "matched_policy"}}); test::AdapterHarness harness(out_conv, out_bindings); harness.SetRequestIds(std::vector{1001}); @@ -608,7 +608,7 @@ TEST_F(AdapterPurityTest, RankedTextBatch policies; policies.emplace_back(0, 0, RankedCandidate("Clause", 1.0f, 1)); - harness.Publish("matched_policies", std::move(policies)); + harness.Publish("matched_policy", std::move(policies)); AuditOutputFixture fix; std::vector outputs = {fix.out}; @@ -623,7 +623,7 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { OutputPortBindings out_bindings( {{"structured_verdicts", "structured_verdicts"}, - {"matched_policies", "matched_policies"}}); + {"matched_policy", "matched_policy"}}); for (const auto parse_status : {JsonParseStatus::kOk, JsonParseStatus::kFailed, @@ -639,7 +639,7 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { {{"risk_level", "SAFE"}, {"risk_score", 0.1f}})); } harness.Publish("structured_verdicts", std::move(verdicts)); - harness.Publish("matched_policies", + harness.Publish("matched_policy", RankedTextBatch{{0, 0, {"req0 first", 1.0f, 1, 1}}, {0, 1, {"req0 second", 0.5f, 2, 2}}, {1, 0, {"req1 first", 1.0f, 1, 1}}}); diff --git a/tests/unit/adapter/test_complex_converters.cpp b/tests/unit/adapter/test_complex_converters.cpp index 07da7042..f4a716a5 100644 --- a/tests/unit/adapter/test_complex_converters.cpp +++ b/tests/unit/adapter/test_complex_converters.cpp @@ -273,7 +273,7 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { RankedTextBatch policies; policies.emplace_back(0, 0, RankedCandidate("Rule 12.3", 0.88f, 1, 0)); - ctx.Publish("matched_policies", std::move(policies)); + ctx.Publish("matched_policy", std::move(policies)); char risk_buf[32] = {0}; CompanyString cs_risk{31, risk_buf}; @@ -297,7 +297,7 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { OutputPortBindings out_bindings( {{"structured_verdicts", "structured_verdicts"}, - {"matched_policies", "matched_policies"}}); + {"matched_policy", "matched_policy"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; @@ -510,25 +510,25 @@ TEST_F(ComplexConvertersTest, AllEightBusinessesRegistered) { } } -// 只声明改名的端口,其余端口映射到同名键。 -TEST_F(ComplexConvertersTest, ComplianceBindingDeclaresOnlyRenamedPort) { +// 审核输出 Converter 的逻辑端口与业务出口同名,Binding 不声明映射。 +TEST_F(ComplexConvertersTest, ComplianceBindingUsesBizPortNames) { const auto* binding = IoBindingRegistry::Instance().FindBinding("dialogue_audit.operator.v1"); ASSERT_NE(binding, nullptr); EXPECT_TRUE(binding->input_ports.empty()); - ASSERT_EQ(binding->output_ports.size(), 1U); + EXPECT_TRUE(binding->output_ports.empty()); const auto* output = IoConverterRegistry::Instance().FindOutputConverter( binding->output_converter_id); ASSERT_NE(output, nullptr); + const auto biz = PipelineCatalog::FindBiz(binding->biz_name); + ASSERT_TRUE(biz.has_value()); - const auto mapping = - EffectivePortMapping(binding->output_ports, output->logical_ports); - ASSERT_EQ(mapping.size(), output->logical_ports.size()); for (const auto& port : output->logical_ports) { - const std::string expected = port.logical_name == "matched_policies" - ? "matched_policy" - : port.logical_name; - EXPECT_EQ(mapping.at(port.logical_name), expected) << port.logical_name; + EXPECT_TRUE(std::any_of(biz->egress.begin(), biz->egress.end(), + [&](const auto& egress) { + return egress.blackboard_key == port.logical_name; + })) + << port.logical_name; } } From 49a59ecf561da4d4a32db50d41ee51176da62910 Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 15:56:33 +0800 Subject: [PATCH 2/3] refactor(adapter): remove IoBinding port rename mappings Converter logical port names are the biz Blackboard keys. Drop the binding input_ports/output_ports, BindIoPort and EffectivePortMapping; the registry audit now checks converter ports against biz ingress/egress names and types directly, and Catalog/validate-io no longer report *_port_mapping. The onboarding guide gains the port naming convention, and its reference table no longer describes the audit converter as using renamed ports. Pipeline JSON is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../edgeflow-adapter-developer/SKILL.md | 2 +- configs/README.md | 2 +- doc/CHANGELOG.md | 4 + doc/dev_guide/business_onboarding.md | 28 +-- doc/dev_guide/source_layout.md | 4 +- doc/developer_guide.md | 6 +- include/adapter/converter_authoring.h | 13 -- include/adapter/io_binding.h | 6 - include/adapter/io_binding_registry.h | 6 - src/adapter/biz/README.md | 2 +- src/adapter/deployment_preparation.cpp | 33 ++-- src/adapter/input/README.md | 2 +- src/adapter/io_binding_registry.cpp | 182 +++++++----------- src/adapter/io_catalog.cpp | 10 - src/cli/alg_pipeline_tool.cpp | 2 - .../catalog/test_catalog_contract_ssot.cpp | 9 - .../operator/test_operator_api.cpp | 2 - tests/unit/adapter/test_adapter_purity.cpp | 8 +- .../unit/adapter/test_complex_converters.cpp | 4 +- .../unit/adapter/test_io_binding_registry.cpp | 123 ++---------- tests/unit/adapter/test_io_converters.cpp | 18 -- tests/unit/adapter/test_text_converters.cpp | 3 - 22 files changed, 139 insertions(+), 330 deletions(-) diff --git a/.agents/skills/edgeflow-adapter-developer/SKILL.md b/.agents/skills/edgeflow-adapter-developer/SKILL.md index 80ab8658..102fb304 100644 --- a/.agents/skills/edgeflow-adapter-developer/SKILL.md +++ b/.agents/skills/edgeflow-adapter-developer/SKILL.md @@ -22,7 +22,7 @@ description: 新增或修改 LLM-EdgeFlow Adapter 的业务输入输出、InputC | --- | --- | | 请求校验、字段选择与内部 payload | `src/adapter/input/`,`InputConverterDefinition` + `REGISTER_INPUT_CONVERTER` | | 完整响应组装、序列化和拷贝 | `src/adapter/output/`,`OutputConverterDefinition` + `REGISTER_OUTPUT_CONVERTER` | -| 新业务边界与连接 | `src/adapter/biz/`,`BizDefinition` 声明 ingress/egress;`IoBindingDefinition` + `REGISTER_IO_BINDING` 选择转换器;批次上限默认 64,只有实测确需更小值时才覆盖;同名端口自动映射,只写不同名的映射 | +| 新业务边界与连接 | `src/adapter/biz/`,`BizDefinition` 声明 ingress/egress;`IoBindingDefinition` + `REGISTER_IO_BINDING` 选择转换器;批次上限默认 64,只有实测确需更小值时才覆盖;转换器端口名即业务键,绑定不做改名 | | 确需新的宿主值类型/分配方式 | `include/adapter/operator_value_type.h`,按 [输出分配指南](../../../doc/dev_guide/operator_output_allocation.md) 注册 | 从 [翻译输入](../../../src/adapter/input/translate_json_input.cpp)、 diff --git a/configs/README.md b/configs/README.md index 4fc21c0e..f0a42d70 100644 --- a/configs/README.md +++ b/configs/README.md @@ -48,7 +48,7 @@ Validator 从输入数据的唯一生产者推导依赖,`depends_on` 仅用于 ## 业务入口与输出配置 每份 Pipeline 显式填写 `deployment.io.io_binding`,它决定外部 C 结构体与内部数据的转换契约。 -框架从注册关系获得业务边界、输入/输出转换器、端口映射和输出类型,不靠文件名或后缀猜测。 +框架从注册关系获得业务边界、输入/输出转换器、端口和输出类型,不靠文件名或后缀猜测。 Demo 根据配置自动选取运行入口,Profile 只保存配置路径、数据集和执行参数。 同一业务的多个 binding 必须声明一致的外部协议、载体和有效槽位,注册审计及部署预检都会校验。 diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 2c1d401b..5ac30435 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +删除 IoBinding 的端口改名映射(`input_ports` / `output_ports`、`BindIoPort`、`EffectivePortMapping`): +转换器逻辑端口名即业务 Blackboard Key,注册审计直接按端口名核对业务出入口与类型;Catalog 与 +`validate-io` 不再输出 `*_port_mapping`。业务接入指南补充端口命名约定。Pipeline JSON 不变。 + 对话合规审核的输出 Converter 直接使用业务出口键 `matched_policy`,删除只为改名存在的 `kMatchedPolicies`(`matched_policies`)及其 Binding 端口映射;Pipeline 配置与外部契约不变。 diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 5c6e1f41..19fc8fb5 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -22,7 +22,7 @@ | `InputConverterDefinition::decode_fn` | 校验外部请求、解析完整载荷、选择业务字段,转换为请求内的中性值发布至 `AlgContext` | | Pipeline / Nodes | 对内部 typed ports 的数据执行算法;可解析模型生成的结构化内容,不承担外部协议转换 | | `OutputConverterDefinition::encode_fn` | 从 `AlgContext` 读取中性结果,按外部契约组装序列化响应并写入已租用输出池 | -| `IoBinding` | 声明业务逻辑端口与 Pipeline Blackboard Key 的映射关系,将转换器与业务编排关联 | +| `IoBinding` | 选择输入/输出转换器并关联业务契约;转换器端口名即 Pipeline Blackboard Key | Demo 输出里的日志、统计和展示字段可以另行组织,但不能为 SDK 补做业务字段提取、 字段改名、响应组装或默认成功结果。宿主程序直接调用 Operator SDK 就应获得约定响应。 @@ -56,7 +56,7 @@ Catalog 的 ingress/egress 是转换器与 Pipeline 之间的内部逻辑端口 “结构体布局相同”不等于“业务契约相同”:同一个 `const char*` 承载纯文本与承载完整 JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代表转换器已支持新协议。 -当前共享 SDK 的 Operator 初始化会全量审计**所有已注册的绑定**:转换器、端口映射、 +当前共享 SDK 的 Operator 初始化会全量审计**所有已注册的绑定**:转换器、端口、 业务契约和批次上限必须完整一致,任何一个绑定不合格,SDK 全局初始化都会失败。 业务能否部署取决于是否注册了绑定;配置引用不存在的绑定时,部署准备报 `UNKNOWN_IO_BINDING`。 @@ -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.operator.v1`)。它关联注册的内部业务边界 +和输入/输出转换器;Demo 从配置自动选择运行入口。Operator 槽位后缀 (`keyword_in` / `keyword_out`)属于宿主调用契约,由绑定关联到已注册宿主类型。 ## 3. 实现并注册转换器与绑定 @@ -129,8 +129,7 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 调用 `PipelineCatalog::RegisterBizDefinition` 登记;业务端口契约不由转换器读写集合推导。 在 `IoBindingDefinition` 中指定 `binding_id`、`biz_name`、`input_converter_id`、 `output_converter_id`,使用 `REGISTER_IO_BINDING` 注册。 - 转换器的逻辑端口默认映射到同名的 Blackboard Key,`input_ports` / `output_ports` - 只写不同名的映射。 + 转换器的逻辑端口名就是 Blackboard Key,绑定不做改名;命名遵循本节后文的端口命名约定。 Binding 的批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖 `max_batch_size`; 转换器只在自身确有限制时才声明上限,0 表示不设限。 有效上限取绑定与两个转换器中正值的最小值,Operator 再按实际输出池深收紧; @@ -143,12 +142,15 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 Operator 的宿主输入校验会拒绝 `CompanyString` 中的原始嵌入 NUL;JSON 文本中的 `\u0000` 转义仍可在解包后成为内部字符串的一部分。 -共享端口用 `MakeBlackboardKey(name)` 定义一次;转换器 Definition 使用 -`RequiredInputPort(port)` / `OutputPort(port)`,回调通过 `bindings.Key(port)` -读取或发布,不能绕过绑定直接读写实际 key。转换器端口直接使用业务出入口的键名,绑定中无需声明; -不要为命名差异引入映射。确需非同名映射时使用 `BindIoPort(logical_port, actual_key)`, -两端的 C++ 类型必须一致。 -需要完整映射的代码调用 `EffectivePortMapping`,不要直接读取绑定的端口表。 +共享端口用 `MakeBlackboardKey(name)` 在 `adapter/biz_blackboard_keys.h` 定义一次;转换器 +Definition 使用 `RequiredInputPort(port)` / `OutputPort(port)`,回调通过 `bindings.Key(port)` +读取或发布。转换器端口名就是业务出入口的 Blackboard Key,绑定不做改名。端口命名约定: + +- 同一业务内同名即同一份数据、同一类型;复用已定义的常量,不重复手写字符串。 +- 可被多个业务复用的转换器使用中性、按角色命名的端口(如 `input_sentences`、`llm_answers`), + 不使用业务专属名称;其他业务复用它时沿用这些名字。 +- 输入侧与输出侧的端口不重名;只有输出转换器有意回传请求数据时才读取入口键。 +- 业务专属转换器直接使用业务键名,例如审核输入的 `user_texts`、`channel_names`。 外部必需槽的常见写法是 `ExternalInputSlot(slot)` 和 `ExternalOutputSlot(slot)`,类型由 traits 推导。 @@ -164,7 +166,7 @@ Operator 的宿主输入校验会拒绝 `CompanyString` 中的原始嵌入 NUL | 规则匹配 | [关键词输入](../../src/adapter/input/text_input.cpp) / [关键词输出](../../src/adapter/output/keyword_result_output.cpp) | 命中与业务状态 | | 完整 JSON 请求响应 | [翻译输入](../../src/adapter/input/translate_json_input.cpp) / [翻译输出](../../src/adapter/output/translation_json_output.cpp) | JSON 字段与响应协议 | | 可选字段、三路结果 | [文档输入](../../src/adapter/input/doc_query_input.cpp) / [文档输出](../../src/adapter/output/doc_answer_output.cpp) | 文档缺省、回答/意图/片段数关联 | -| 风险判定、非同名端口 | [审核输入](../../src/adapter/input/audit_input.cpp) / [审核输出](../../src/adapter/output/audit_result_output.cpp) | 风险分数/枚举与排名校验 | +| 风险判定、排名首项 | [审核输入](../../src/adapter/input/audit_input.cpp) / [审核输出](../../src/adapter/output/audit_result_output.cpp) | 风险分数/枚举与排名校验 | | 音频、两路结果 | [音频输入](../../src/adapter/input/audio_input.cpp) / [音频输出](../../src/adapter/output/audio_result_output.cpp) | PCM 与采样率、转写/意图组合 | | 多个外部槽 | [图像问题输入](../../src/adapter/input/image_query_input.cpp) / [票据输出](../../src/adapter/output/invoice_result_output.cpp) | frame 的请求 ID、票据与 OCR boxes | | 候选展开与排名 | [重排输入](../../src/adapter/input/rerank_input.cpp) / [重排输出](../../src/adapter/output/rerank_result_output.cpp) | sub_id、排名和原始索引恢复 | diff --git a/doc/dev_guide/source_layout.md b/doc/dev_guide/source_layout.md index 0686be04..acd395a6 100644 --- a/doc/dev_guide/source_layout.md +++ b/doc/dev_guide/source_layout.md @@ -86,8 +86,8 @@ src/adapter/ 常见单槽、每请求一行的回调使用 `DecodeRequestRows` / `EncodeResultRows` 调用普通业务函数, 批次与绑定归辅助层;多槽、展开和汇聚保留显式算法。 各业务接入绑定在 `src/adapter/biz/` 中声明 `IoBindingDefinition`,通过 `REGISTER_IO_BINDING` 注册。 -端口 Definition、回调的 `bindings.Key(port)` 和 `BindIoPort` 映射共用 typed 声明; -非同名映射显式传入逻辑端口与实际 key。常见必需槽可用 `ExternalInputSlot` / +端口 Definition 与回调的 `bindings.Key(port)` 共用 typed 声明,端口名即业务键名, +绑定不做改名。常见必需槽可用 `ExternalInputSlot` / `ExternalOutputSlot` 推导类型和默认同名后缀,输出容量字段由已注册 ValueType 决定; 特殊布局仍使用完整定义。 业务专属实现可按修改关联同文件组织,共享 converter 保留独立引用;不要求为每个业务创建聚合宏或新注册表。 diff --git a/doc/developer_guide.md b/doc/developer_guide.md index 6de1967c..83c6bcd0 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -42,8 +42,8 @@ Backend;出现调度、模型语义或硬件能力缺口时,再查阅相应 C++ `NamedIoBatch` 是算法的公开 Process 边界。`OperatorValueTypeRegistry` 注册 外部类型、规范后缀及校验/分配生命周期;`InputConverter` 负责读取和深拷贝完整请求, -`OutputConverter` 使用 Create 期输出池组装完整响应。`IoBindingDefinition` 声明业务、 -转换器、逻辑槽位与内部端口映射,注册审计检查类型和契约一致性。 +`OutputConverter` 使用 Create 期输出池组装完整响应。`IoBindingDefinition` 声明业务与 +转换器,注册审计检查端口、类型和契约一致性。 `CompanyString` 按 `length` 表达文本;Operator 输入校验拒绝原始嵌入 NUL,JSON 中 转义的 NUL 可在解包后保留。输出按显式长度复制,二进制内容使用 `CompanyBuffer`。 @@ -81,7 +81,7 @@ CrossRerank 的排名数组和 Compliance 的首项选择使用 `N:1 / aggregate `AdapterValidationHelper` 完成批次、指针和长度校验,发布中性数据至 `AlgContext`。 3. 在 `src/adapter/output/` 实现 `OutputConverter`,完成输出结构租约组装与容量检查。 4. 在 `src/adapter/biz/` 声明 `BizDefinition` 并实现 `IoBinding` 绑定:选择转换器、 - 批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖;逻辑端口默认映射到同名 Blackboard Key,只写不同名的映射。 + 批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖;转换器逻辑端口名即 Blackboard Key,绑定不做改名。 5. 解码与编码使用 `core/common_contracts.h` 中的中性值类型,并在 `adapter/biz_blackboard_keys.h` 集中声明业务 ingress/egress `BlackboardKey`; Core、Node 和 Engine 不得包含该业务 key 头。 diff --git a/include/adapter/converter_authoring.h b/include/adapter/converter_authoring.h index 80095ac5..755d650c 100644 --- a/include/adapter/converter_authoring.h +++ b/include/adapter/converter_authoring.h @@ -17,19 +17,6 @@ namespace llm_edgeflow { -// 两侧必须是同一 C++ 值类型;恒等映射需显式声明。 -template -inline std::pair BindIoPort( - const BlackboardKey& logical_port, const BlackboardKey& actual_key) { - return {logical_port.name, actual_key.name}; -} - -template -inline std::pair BindIoPort( - const BlackboardKey& port) { - return BindIoPort(port, port); -} - // 常用约定:必填槽位,type_suffix = slot_name,key_suffix 为空时回退到 // type_suffix。其他后缀或可选槽位请使用 ExternalSlotDefinition 的显式字段。 template diff --git a/include/adapter/io_binding.h b/include/adapter/io_binding.h index 5db49536..6dea36d8 100644 --- a/include/adapter/io_binding.h +++ b/include/adapter/io_binding.h @@ -2,8 +2,6 @@ #include #include -#include -#include namespace llm_edgeflow { @@ -19,10 +17,6 @@ struct IoBindingDefinition { std::string input_converter_id; std::string output_converter_id; - // logical_name -> blackboard_key。未列出的 Converter 逻辑端口映射到同名键, - // 只需列出改名的端口。 - std::unordered_map input_ports; - std::unordered_map output_ports; // 默认使用 Operator 标准上限;0 表示不设上限。有效上限取 binding 及其 // Converter 中最小的正值,且至少一方须为正。 size_t max_batch_size = kDefaultIoBindingMaxBatchSize; diff --git a/include/adapter/io_binding_registry.h b/include/adapter/io_binding_registry.h index 8e67d86d..bb915bee 100644 --- a/include/adapter/io_binding_registry.h +++ b/include/adapter/io_binding_registry.h @@ -11,12 +11,6 @@ namespace llm_edgeflow { -// 已声明的条目原样保留;未声明的 Converter 逻辑端口映射到同名键。 -// 使用时计算,与注册顺序无关。 -std::unordered_map EffectivePortMapping( - const std::unordered_map& declared, - const std::vector& logical_ports); - // 输出槽位按字典序返回其已注册 ValueType 的字符串容量字段; // 输入槽位和未知 ValueType 返回空列表。 std::vector EffectiveCapacityFields( diff --git a/src/adapter/biz/README.md b/src/adapter/biz/README.md index 1bc82618..e498fa2f 100644 --- a/src/adapter/biz/README.md +++ b/src/adapter/biz/README.md @@ -6,5 +6,5 @@ - 每个业务绑定文件通过 `BizDefinition` 声明业务名、Demo 名及完整 ingress/egress Blackboard 契约,并调用 `PipelineCatalog::RegisterBizDefinition` 登记。 - 显式声明该业务支持的 Operator 绑定,选择独立的输入/输出转换器,绑定批次上限默认为框架标准值 64,只有实测确需更小值时才覆盖 `max_batch_size`;转换器只在自身确有限制时才声明上限。 - 业务配置通过 Pipeline 的 `deployment.io.io_binding` 显式选择全局唯一的 `binding_id`。 -- 转换器的逻辑端口默认映射到同名 Blackboard Key;只有非同名映射才写 `BindIoPort(logical_port, actual_key)`,复用转换器声明的名字和类型,不重复手写字符串对。 +- 转换器的逻辑端口名即 Blackboard Key,绑定不做改名;复用转换器时沿用它声明的端口名,命名约定见[业务接入指南](../../../doc/dev_guide/business_onboarding.md#3-实现并注册转换器与绑定)。 - 保留独立转换器及完整业务 ingress/egress;不能仅根据 converter 读写集合推导全部业务契约。 diff --git a/src/adapter/deployment_preparation.cpp b/src/adapter/deployment_preparation.cpp index 704133dc..48689471 100644 --- a/src/adapter/deployment_preparation.cpp +++ b/src/adapter/deployment_preparation.cpp @@ -13,6 +13,18 @@ #include "core/pipeline_config.h" namespace llm_edgeflow { +namespace { + +// Converter 的逻辑端口名就是 Blackboard 键。 +std::unordered_map SameNamedKeys( + const std::vector& ports) { + std::unordered_map keys; + for (const auto& port : ports) + keys.emplace(port.logical_name, port.logical_name); + return keys; +} + +} // namespace bool PrepareDeploymentDocument(const nlohmann::json& document, const DeploymentPrepareOptions& options, @@ -214,33 +226,28 @@ bool PrepareDeploymentDocument(const nlohmann::json& document, resolved_neutral_json = std::move(doc_split.neutral_pipeline_json); } - // S6: 构造中性 I/O 边界,发布准备结果(未显式映射的端口按同名映射) - auto input_mapping = - EffectivePortMapping(binding->input_ports, in_conv->logical_ports); - auto output_mapping = - EffectivePortMapping(binding->output_ports, out_conv->logical_ports); + // S6: 构造中性 I/O 边界,发布准备结果;Converter 端口名即 Blackboard 键 PipelineIoBoundary io_boundary; for (const auto& port : in_conv->logical_ports) { io_boundary.input_published_ports.emplace_back( - input_mapping.at(port.logical_name), port.type_id, port.required, - port.cardinality, port.provenance_policy, port.lifetime, - port.lifetime_config_field); + port.logical_name, port.type_id, port.required, port.cardinality, + port.provenance_policy, port.lifetime, port.lifetime_config_field); } for (const auto& port : out_conv->logical_ports) { io_boundary.output_consumed_ports.emplace_back( - output_mapping.at(port.logical_name), port.type_id, port.required, - port.cardinality, port.provenance_policy, port.lifetime, - port.lifetime_config_field); + port.logical_name, port.type_id, port.required, port.cardinality, + port.provenance_policy, port.lifetime, port.lifetime_config_field); } PreparedDeployment local_prep; local_prep.binding = *binding; local_prep.input_converter = in_conv; local_prep.output_converter = out_conv; - local_prep.input_port_bindings = InputPortBindings(std::move(input_mapping)); + local_prep.input_port_bindings = + InputPortBindings(SameNamedKeys(in_conv->logical_ports)); local_prep.output_port_bindings = - OutputPortBindings(std::move(output_mapping)); + OutputPortBindings(SameNamedKeys(out_conv->logical_ports)); local_prep.effective_max_batch_size = max_batch; local_prep.output_specs = std::move(local_output_specs); local_prep.output_parameter_texts = std::move(local_output_params); diff --git a/src/adapter/input/README.md b/src/adapter/input/README.md index 3b7a6f2d..f0c3a642 100644 --- a/src/adapter/input/README.md +++ b/src/adapter/input/README.md @@ -8,6 +8,6 @@ - 单槽、每请求一个载荷使用 `DecodeRequestRows`;业务函数接收宿主值、返回自持有载荷与 `AdapterStatus`,框架处理批次、来源、绑定及错误位置。文本和 PCM 是已编译示例。 - 多槽和候选展开继续显式使用 `ValidateDecodeRequest` / `ReadInputSlot`;批次上限默认采用绑定的框架标准值 64、Operator 经 `InputDecodeOptions` 传入,转换器不另设常量。文本结构检查和复制使用 `IsValidInputString` / `CopyInputString`。 - 输入长度上限引用 `adapter/biz_input_constraints.h`;只有业务确需更严格的限制时,才在转换器中定义具名常量。rerank 候选段落保留 64 KiB 的转换器限制。 -- 负责外部载体批次envelope校验、类型转换、深拷贝(copy-in)以及按端口映射发布到 `AlgContext`。 +- 负责外部载体批次envelope校验、类型转换、深拷贝(copy-in)以及按声明的端口发布到 `AlgContext`。 - 保证无请求间共享状态与局部临时引用的生命周期隔离。 - 输入校验失败时立即终止,不租用输出池块,不触发 Pipeline 执行;输出池存储已在 Create 阶段分配。 diff --git a/src/adapter/io_binding_registry.cpp b/src/adapter/io_binding_registry.cpp index fef07cd8..fee6da68 100644 --- a/src/adapter/io_binding_registry.cpp +++ b/src/adapter/io_binding_registry.cpp @@ -86,16 +86,6 @@ std::vector EffectiveCapacityFields( return fields; } -std::unordered_map EffectivePortMapping( - const std::unordered_map& declared, - const std::vector& logical_ports) { - auto mapping = declared; - for (const auto& port : logical_ports) { - mapping.emplace(port.logical_name, port.logical_name); - } - return mapping; -} - size_t EffectiveMaxBatchSize(const IoBindingDefinition& binding, const InputConverterDefinition& input, const OutputConverterDefinition& output) { @@ -216,134 +206,96 @@ bool IoBindingRegistry::Audit(std::vector* out_errors) const { "' references unregistered biz_name: " + binding.biz_name); } - // 2. 检查 input converter;未显式映射的逻辑端口按同名映射 + // 2. 检查 input converter:逻辑端口名即业务入口键 const auto* in_conv = conv_reg.FindInputConverter(binding.input_converter_id); if (!in_conv) { errors.push_back("Binding '" + binding_id + "' references unregistered input_converter: " + binding.input_converter_id); - } else { - for (const auto& entry : binding.input_ports) { - const auto& logical_name = entry.first; - const bool advertised = std::any_of( - in_conv->logical_ports.begin(), in_conv->logical_ports.end(), - [&logical_name](const auto& p) { - return p.logical_name == logical_name; - }); - if (!advertised) { - errors.push_back( - "Binding '" + binding_id + - "' maps unadvertised input logical port: " + logical_name); + } else if (biz_def) { + for (const auto& port : in_conv->logical_ports) { + const auto& target_key = port.logical_name; + auto ingress_it = + std::find_if(biz_def->ingress.begin(), biz_def->ingress.end(), + [&target_key](const auto& p) { + return p.blackboard_key == target_key; + }); + if (ingress_it == biz_def->ingress.end()) { + errors.push_back("Binding '" + binding_id + "' 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 '" + + port.logical_name + "' type '" + port.type_id + + "' does not match biz ingress key '" + target_key + + "' type '" + ingress_it->type_id + "'"); } } - const auto mapping = - EffectivePortMapping(binding.input_ports, in_conv->logical_ports); - if (biz_def) { - for (const auto& port : in_conv->logical_ports) { - const auto& target_key = mapping.at(port.logical_name); - auto ingress_it = - std::find_if(biz_def->ingress.begin(), biz_def->ingress.end(), - [&target_key](const auto& p) { - return p.blackboard_key == target_key; - }); - if (ingress_it == biz_def->ingress.end()) { - errors.push_back( - "Binding '" + binding_id + "' input port '" + - port.logical_name + - "' maps to non-existent biz ingress key: " + target_key); - } else if (port.type_id != ingress_it->type_id) { - errors.push_back("Binding '" + binding_id + "' input port '" + - port.logical_name + "' type '" + port.type_id + - "' does not match biz ingress key '" + target_key + - "' type '" + ingress_it->type_id + "'"); - } - } - - for (const auto& ingress_port : biz_def->ingress) { - if (!ingress_port.required) continue; - const bool covered = std::any_of( - mapping.begin(), mapping.end(), [&ingress_port](const auto& kv) { - return kv.second == ingress_port.blackboard_key; - }); - if (!covered) { - errors.push_back("Binding '" + binding_id + - "' missing required biz ingress port: " + - ingress_port.blackboard_key); - } + for (const auto& ingress_port : biz_def->ingress) { + if (!ingress_port.required) continue; + const bool covered = std::any_of( + in_conv->logical_ports.begin(), in_conv->logical_ports.end(), + [&ingress_port](const auto& port) { + return port.logical_name == ingress_port.blackboard_key; + }); + if (!covered) { + errors.push_back("Binding '" + binding_id + + "' missing required biz ingress port: " + + ingress_port.blackboard_key); } } } - // 3. 检查 output converter;未显式映射的逻辑端口按同名映射 + // 3. 检查 output converter:逻辑端口名即业务出口(或入口)键 const auto* out_conv = conv_reg.FindOutputConverter(binding.output_converter_id); if (!out_conv) { errors.push_back("Binding '" + binding_id + "' references unregistered output_converter: " + binding.output_converter_id); - } else { - for (const auto& entry : binding.output_ports) { - const auto& logical_name = entry.first; - const bool advertised = std::any_of( - out_conv->logical_ports.begin(), out_conv->logical_ports.end(), - [&logical_name](const auto& p) { - return p.logical_name == logical_name; - }); - if (!advertised) { - errors.push_back( - "Binding '" + binding_id + - "' maps unadvertised output logical port: " + logical_name); - } - } - - const auto mapping = - EffectivePortMapping(binding.output_ports, out_conv->logical_ports); - if (biz_def) { - for (const auto& port : out_conv->logical_ports) { - const auto& target_key = mapping.at(port.logical_name); - const auto matches_key = [&target_key](const auto& p) { - return p.blackboard_key == target_key; - }; - auto egress_it = std::find_if(biz_def->egress.begin(), - biz_def->egress.end(), matches_key); - auto ingress_it = std::find_if(biz_def->ingress.begin(), - 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 '" + - 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 '" + - port.logical_name + "' type '" + port.type_id + - "' does not match biz ingress key '" + - target_key + "' type '" + ingress_it->type_id + - "'"); - } - } else { + } else if (biz_def) { + for (const auto& port : out_conv->logical_ports) { + const auto& target_key = port.logical_name; + const auto matches_key = [&target_key](const auto& p) { + return p.blackboard_key == target_key; + }; + auto egress_it = std::find_if(biz_def->egress.begin(), + biz_def->egress.end(), matches_key); + auto ingress_it = std::find_if(biz_def->ingress.begin(), + 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 '" + - port.logical_name + - "' maps to non-existent biz key: " + target_key); + 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 '" + + 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 '" + + port.logical_name + + "' is not a biz egress or ingress key"); } + } - for (const auto& egress_port : biz_def->egress) { - if (!egress_port.required) continue; - const bool covered = std::any_of( - mapping.begin(), mapping.end(), [&egress_port](const auto& kv) { - return kv.second == egress_port.blackboard_key; - }); - if (!covered) { - errors.push_back("Binding '" + binding_id + - "' missing required biz egress port: " + - egress_port.blackboard_key); - } + for (const auto& egress_port : biz_def->egress) { + if (!egress_port.required) continue; + const bool covered = std::any_of( + out_conv->logical_ports.begin(), out_conv->logical_ports.end(), + [&egress_port](const auto& port) { + return port.logical_name == egress_port.blackboard_key; + }); + if (!covered) { + errors.push_back("Binding '" + binding_id + + "' missing required biz egress port: " + + egress_port.blackboard_key); } } } diff --git a/src/adapter/io_catalog.cpp b/src/adapter/io_catalog.cpp index 200c263f..dcac3486 100644 --- a/src/adapter/io_catalog.cpp +++ b/src/adapter/io_catalog.cpp @@ -55,21 +55,11 @@ nlohmann::json OutputConverterToJson(const OutputConverterDefinition& conv) { } nlohmann::json IoBindingToJson(const IoBindingDefinition& b) { - // 报告有效映射,包括映射到同名键的端口。 - const auto& converters = IoConverterRegistry::Instance(); - const auto* input = converters.FindInputConverter(b.input_converter_id); - const auto* output = converters.FindOutputConverter(b.output_converter_id); return {{"binding_id", b.binding_id}, {"biz_name", b.biz_name}, {"transport", "operator"}, {"input_converter_id", b.input_converter_id}, {"output_converter_id", b.output_converter_id}, - {"input_port_mapping", - input ? EffectivePortMapping(b.input_ports, input->logical_ports) - : b.input_ports}, - {"output_port_mapping", - output ? EffectivePortMapping(b.output_ports, output->logical_ports) - : b.output_ports}, {"max_batch_size", b.max_batch_size}}; } diff --git a/src/cli/alg_pipeline_tool.cpp b/src/cli/alg_pipeline_tool.cpp index 7a724a39..4e2d97dd 100644 --- a/src/cli/alg_pipeline_tool.cpp +++ b/src/cli/alg_pipeline_tool.cpp @@ -649,8 +649,6 @@ int main(int argc, char* argv[]) { {"transport", "operator"}, {"input_converter_id", plan->binding.input_converter_id}, {"output_converter_id", plan->binding.output_converter_id}, - {"input_port_mapping", plan->input_port_bindings.All()}, - {"output_port_mapping", plan->output_port_bindings.All()}, {"effective_max_batch_size", plan->effective_max_batch_size}, {"external_input_type", plan->input_converter diff --git a/tests/contract/catalog/test_catalog_contract_ssot.cpp b/tests/contract/catalog/test_catalog_contract_ssot.cpp index 6c42d673..0a133454 100644 --- a/tests/contract/catalog/test_catalog_contract_ssot.cpp +++ b/tests/contract/catalog/test_catalog_contract_ssot.cpp @@ -300,15 +300,6 @@ TEST_F(CatalogContractSsotTest, CatalogHasNoRequestIdPort) { } } } - for (const auto& binding : catalog.at("io_bindings")) { - for (const char* kind : {"input_port_mapping", "output_port_mapping"}) { - for (const auto& mapping : binding.at(kind).items()) { - EXPECT_NE(mapping.key(), "raw_request_ids") << binding.at("binding_id"); - EXPECT_NE(mapping.value(), "raw_request_ids") - << binding.at("binding_id"); - } - } - } } TEST_F(CatalogContractSsotTest, IoCatalogExportsKeywordSlotNamesAndTypes) { diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 0ec1b9e1..ccf49f5f 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -2451,8 +2451,6 @@ const bool g_reg_nested_output_components = []() { bind.input_converter_id = "keyword.plain.operator.v1"; bind.output_converter_id = "test_nested_output.operator.v1"; - bind.input_ports = {{"input_sentences", "input_sentences"}}; - bind.output_ports = {{"rule_matches", "rule_matches"}}; bind.max_batch_size = 64; IoBindingRegistry::Instance().RegisterBinding(bind); return true; diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 224a5a65..582b9477 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -1008,8 +1008,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { { test::AdapterHarness harness( entity_conv, - InputPortBindings(EffectivePortMapping(entity_binding->input_ports, - entity_conv->logical_ports))); + InputPortBindings({{"input_sentences", "input_sentences"}})); std::string text_str = "entity sentence"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -1025,8 +1024,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { { test::AdapterHarness harness( keyword_conv, - InputPortBindings(EffectivePortMapping(keyword_binding->input_ports, - keyword_conv->logical_ports))); + InputPortBindings({{"input_sentences", "input_sentences"}})); std::string text_str = "keyword sentence"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -1046,8 +1044,6 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { test_reuse_binding.input_converter_id = "text.plain.operator.v1"; test_reuse_binding.output_converter_id = "document.structured.operator.v1"; - test_reuse_binding.input_ports = entity_binding->input_ports; - test_reuse_binding.output_ports = entity_binding->output_ports; test_reuse_binding.max_batch_size = 64; IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding); diff --git a/tests/unit/adapter/test_complex_converters.cpp b/tests/unit/adapter/test_complex_converters.cpp index f4a716a5..bb58b4c6 100644 --- a/tests/unit/adapter/test_complex_converters.cpp +++ b/tests/unit/adapter/test_complex_converters.cpp @@ -510,13 +510,11 @@ TEST_F(ComplexConvertersTest, AllEightBusinessesRegistered) { } } -// 审核输出 Converter 的逻辑端口与业务出口同名,Binding 不声明映射。 +// 审核输出 Converter 的逻辑端口与业务出口同名。 TEST_F(ComplexConvertersTest, ComplianceBindingUsesBizPortNames) { const auto* binding = IoBindingRegistry::Instance().FindBinding("dialogue_audit.operator.v1"); ASSERT_NE(binding, nullptr); - EXPECT_TRUE(binding->input_ports.empty()); - EXPECT_TRUE(binding->output_ports.empty()); const auto* output = IoConverterRegistry::Instance().FindOutputConverter( binding->output_converter_id); ASSERT_NE(output, nullptr); diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index a7ca6f1a..480d3abd 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -92,7 +92,7 @@ class IoBindingRegistryTest : public ::testing::Test { PortDirection::kInput, true, "entity_in")}; in_def.max_batch_size = 64; in_def.logical_ports = { - NodePortDefinition("texts", "TextBatch", true, "1:1")}; + NodePortDefinition("input_sentences", "TextBatch", true, "1:1")}; in_def.decode_fn = &DummyDecode; IoConverterRegistry::Instance().RegisterInputConverter(in_def); @@ -105,7 +105,7 @@ class IoBindingRegistryTest : public ::testing::Test { PortDirection::kOutput, true, "entity_out")}; out_def.max_batch_size = 64; out_def.logical_ports = { - NodePortDefinition("answers", "TextBatch", true, "1:1")}; + NodePortDefinition("llm_answers", "TextBatch", true, "1:1")}; out_def.encode_fn = &DummyEncode; IoConverterRegistry::Instance().RegisterOutputConverter(out_def); @@ -129,12 +129,22 @@ class IoBindingRegistryTest : public ::testing::Test { binding.input_converter_id = "test.in.operator"; binding.output_converter_id = "test.out.operator"; - binding.input_ports = {{"texts", "input_sentences"}}; - binding.output_ports = {{"answers", "llm_answers"}}; binding.max_batch_size = max_batch_size; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); } + // 端口 texts 不在业务契约中,必需的 input_sentences 也无人覆盖。 + std::string RegisterUnmatchedInputConverter() { + auto converter = + *IoConverterRegistry::Instance().FindInputConverter("test.in.operator"); + converter.converter_id = "unmatched.in.operator"; + converter.logical_ports = { + NodePortDefinition("texts", "TextBatch", true, "1:1")}; + EXPECT_TRUE( + IoConverterRegistry::Instance().RegisterInputConverter(converter)); + return converter.converter_id; + } + static inline std::vector saved_bindings_; static inline std::vector saved_inputs_; static inline std::vector saved_outputs_; @@ -175,8 +185,6 @@ TEST_F(IoBindingRegistryTest, RegisterAndAuditValidBinding) { binding.input_converter_id = "test.in.operator"; binding.output_converter_id = "test.out.operator"; - binding.input_ports = {{"texts", "input_sentences"}}; - binding.output_ports = {{"answers", "llm_answers"}}; EXPECT_TRUE(reg.RegisterBinding(binding)); @@ -273,8 +281,6 @@ TEST_F(IoBindingRegistryTest, BindingWithoutExplicitLimitUsesStandardDefault) { binding.biz_name = "test_biz_v1"; binding.input_converter_id = input.converter_id; binding.output_converter_id = output.converter_id; - binding.input_ports = {{"texts", "input_sentences"}}; - binding.output_ports = {{"answers", "llm_answers"}}; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); std::vector errors; EXPECT_TRUE(IoBindingRegistry::Instance().Audit(&errors)); @@ -316,79 +322,6 @@ TEST_F(IoBindingRegistryTest, CapacityFieldsDeriveFromValueType) { EXPECT_TRUE(EffectiveCapacityFields(unknown).empty()); } -TEST_F(IoBindingRegistryTest, OmittedPortMappingsUseConverterPortNames) { - auto input = - *IoConverterRegistry::Instance().FindInputConverter("test.in.operator"); - auto output = - *IoConverterRegistry::Instance().FindOutputConverter("test.out.operator"); - input.converter_id = "same_name.in.operator"; - input.logical_ports = { - NodePortDefinition("input_sentences", "TextBatch", true, "1:1")}; - input.max_batch_size = 0; - output.converter_id = "same_name.out.operator"; - output.logical_ports = { - NodePortDefinition("llm_answers", "TextBatch", true, "1:1")}; - output.max_batch_size = 0; - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterInputConverter(input)); - ASSERT_TRUE(IoConverterRegistry::Instance().RegisterOutputConverter(output)); - - IoBindingDefinition binding; - binding.binding_id = "same_name.operator.v1"; - binding.biz_name = "test_biz_v1"; - binding.input_converter_id = input.converter_id; - binding.output_converter_id = output.converter_id; - binding.max_batch_size = 16; - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); - - std::vector errors; - EXPECT_TRUE(IoBindingRegistry::Instance().Audit(&errors)) - << (errors.empty() ? "" : errors.front()); - - const nlohmann::json document = { - {"deployment", {{"io", {{"io_binding", binding.binding_id}}}}}, - {"pipeline", DefaultPipelineNodes()}}; - PreparedDeployment prepared; - DeploymentDiagnostic diagnostic; - ASSERT_TRUE(PrepareDeploymentDocument(document, {}, &prepared, &diagnostic)) - << diagnostic.message; - EXPECT_EQ(prepared.effective_max_batch_size, 16U); - EXPECT_EQ(prepared.input_port_bindings.GetActualKey("input_sentences"), - "input_sentences"); - EXPECT_EQ(prepared.output_port_bindings.GetActualKey("llm_answers"), - "llm_answers"); - - const auto catalog = IoCatalog::ToJson(); - const auto entry = - std::find_if(catalog["io_bindings"].begin(), catalog["io_bindings"].end(), - [&](const auto& item) { - return item["binding_id"] == binding.binding_id; - }); - ASSERT_NE(entry, catalog["io_bindings"].end()); - EXPECT_EQ((*entry)["input_port_mapping"], - nlohmann::json({{"input_sentences", "input_sentences"}})); - EXPECT_EQ((*entry)["output_port_mapping"], - nlohmann::json({{"llm_answers", "llm_answers"}})); -} - -TEST_F(IoBindingRegistryTest, AuditRejectsMappingOfUnadvertisedPort) { - IoBindingDefinition binding; - binding.binding_id = "unknown_port.operator.v1"; - binding.biz_name = "test_biz_v1"; - binding.input_converter_id = "test.in.operator"; - binding.output_converter_id = "test.out.operator"; - binding.input_ports = {{"texts", "input_sentences"}, - {"unknown", "input_sentences"}}; - binding.output_ports = {{"answers", "llm_answers"}}; - ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); - - std::vector errors; - EXPECT_FALSE(IoBindingRegistry::Instance().Audit(&errors)); - EXPECT_TRUE(std::any_of(errors.begin(), errors.end(), [](const auto& e) { - return e.find("maps unadvertised input logical port: unknown") != - std::string::npos; - })); -} - TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { auto& reg = IoBindingRegistry::Instance(); @@ -399,8 +332,6 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { valid_binding.input_converter_id = "test.in.operator"; valid_binding.output_converter_id = "test.out.operator"; - valid_binding.input_ports = {{"texts", "input_sentences"}}; - valid_binding.output_ports = {{"answers", "llm_answers"}}; EXPECT_TRUE(reg.RegisterBinding(valid_binding)); // 单独 audit 合法绑定应当通过 @@ -408,29 +339,26 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { EXPECT_TRUE(reg.Audit(&errors)); EXPECT_TRUE(errors.empty()); - // 2. 注册未被选择使用的非法绑定:texts 未显式映射时按同名映射, - // 而业务契约中没有 texts,必需的 input_sentences 也无人覆盖 + // 2. 注册未被选择使用的非法绑定:输入 Converter 的端口不在业务契约中 IoBindingDefinition illegal_binding; illegal_binding.binding_id = "unselected_bad.operator.v1"; illegal_binding.biz_name = "test_biz_v1"; - illegal_binding.input_converter_id = "test.in.operator"; + illegal_binding.input_converter_id = RegisterUnmatchedInputConverter(); illegal_binding.output_converter_id = "test.out.operator"; - illegal_binding.input_ports = {}; - illegal_binding.output_ports = {{"answers", "llm_answers"}}; EXPECT_TRUE(reg.RegisterBinding(illegal_binding)); // 全量审计必须被这个未被选中的非法绑定阻断 errors.clear(); EXPECT_FALSE(reg.Audit(&errors)); - bool found_missing_port_mapping = false; + bool found_missing_ingress = false; for (const auto& e : errors) { if (e.find("missing required biz ingress port: input_sentences") != std::string::npos) { - found_missing_port_mapping = true; + found_missing_ingress = true; } } - EXPECT_TRUE(found_missing_port_mapping); + EXPECT_TRUE(found_missing_ingress); // 验证 SharedAlgorithmRuntime::GlobalInit() 也会因为 Audit 失败而返回冲突错误 // (-6) @@ -647,20 +575,15 @@ TEST_F(IoBindingRegistryTest, FailClosedAuditRejectsInvalidUnselectedBinding) { valid_binding.input_converter_id = "test.in.operator"; valid_binding.output_converter_id = "test.out.operator"; - valid_binding.input_ports = {{"texts", "input_sentences"}}; - valid_binding.output_ports = {{"answers", "llm_answers"}}; 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 = "test.in.operator"; + unselected_bad_binding.input_converter_id = RegisterUnmatchedInputConverter(); unselected_bad_binding.output_converter_id = "test.out.operator"; - // 故意遗漏必需输入映射 texts - unselected_bad_binding.input_ports = {}; - unselected_bad_binding.output_ports = {{"answers", "llm_answers"}}; EXPECT_TRUE(reg.RegisterBinding(unselected_bad_binding)); // 全量 Audit 必须对所有已注册绑定实行 Fail-Closed 检查 @@ -1132,8 +1055,6 @@ TEST_F(IoBindingRegistryTest, DefaultsAndOverridesKeepOptionalOutputOptIn) { binding.biz_name = "test_biz_v1"; binding.input_converter_id = "test.in.operator"; binding.output_converter_id = converter.converter_id; - binding.input_ports = {{"texts", "input_sentences"}}; - binding.output_ports = {{"answers", "llm_answers"}}; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(binding)); nlohmann::json document = {{"pipeline", DefaultPipelineNodes()}}; @@ -1293,8 +1214,6 @@ TEST_F(IoBindingRegistryTest, first.biz_name = "test_biz_v1"; first.input_converter_id = input.converter_id; first.output_converter_id = output.converter_id; - first.input_ports = {{"texts", "input_sentences"}}; - first.output_ports = {{"answers", "llm_answers"}}; ASSERT_TRUE(IoBindingRegistry::Instance().RegisterBinding(first)); input.converter_id = "equivalent.input.second"; diff --git a/tests/unit/adapter/test_io_converters.cpp b/tests/unit/adapter/test_io_converters.cpp index e3cc0689..02fd206f 100644 --- a/tests/unit/adapter/test_io_converters.cpp +++ b/tests/unit/adapter/test_io_converters.cpp @@ -432,24 +432,6 @@ TEST(IoConverterTest, HarnessSingleSlotUsesDeclaredSlotType) { EXPECT_EQ(outputs.size(), 1U); } -TEST(IoConverterTest, TypedBindingsResolveNonIdentityPorts) { - constexpr auto logical = MakeBlackboardKey("logical_text"); - constexpr auto actual = MakeBlackboardKey("storage_text"); - InputPortBindings inputs({BindIoPort(logical, actual)}); - OutputPortBindings outputs({BindIoPort(logical, actual)}); - AlgContext context; - ASSERT_TRUE(context.Publish(inputs.Key(logical), TextBatch{{0, 0, "hello"}})); - const auto* value = context.Read(outputs.Key(logical)); - ASSERT_NE(value, nullptr); - ASSERT_EQ(value->size(), 1U); - EXPECT_EQ(value->front().data, "hello"); - EXPECT_EQ(context.Read(logical), nullptr); - EXPECT_STREQ(inputs.Key(logical).name, "storage_text"); - EXPECT_STREQ(outputs.Key(logical).name, "storage_text"); - EXPECT_EQ(BindIoPort(logical), std::make_pair(std::string("logical_text"), - std::string("logical_text"))); -} - TEST(IoConverterTest, OutputWriterRequiresExplicitFieldCapacityWithoutWriting) { TestOutputBatchView view; OutputEncodeOptions options; diff --git a/tests/unit/adapter/test_text_converters.cpp b/tests/unit/adapter/test_text_converters.cpp index 3ad57186..da21786d 100644 --- a/tests/unit/adapter/test_text_converters.cpp +++ b/tests/unit/adapter/test_text_converters.cpp @@ -194,9 +194,6 @@ TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { test_reuse_binding.input_converter_id = "text.plain.operator.v1"; test_reuse_binding.output_converter_id = "document.structured.operator.v1"; - test_reuse_binding.input_ports = {{"input_sentences", "input_sentences"}}; - test_reuse_binding.output_ports = { - {"extracted_entities", "extracted_entities"}}; test_reuse_binding.max_batch_size = 64; IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding); From 19481173e2614c4495384b2c00f43f3823d134ce Mon Sep 17 00:00:00 2001 From: chamsechan Date: Mon, 5 Oct 2026 16:05:29 +0800 Subject: [PATCH 3/3] refactor(adapter): drop PortBindings from converter callbacks With rename mappings gone, PortBindings only resolved each port to itself. Remove InputPortBindings/OutputPortBindings and the bindings parameter from DecodeInputFn/EncodeOutputFn, DecodeRequestRows, EncodeResultRows and ReadOutputValue; converters now read and publish their port constants directly, and PreparedDeployment no longer carries port bindings. A write to an undeclared port no longer lands silently on an empty key. Tests use the port keys directly; the row-helper tests now assert against the real port key, so the no-publish-on-failure check is no longer vacuous. Docs drop the remaining references to binding port mappings (including two missed in the previous commit), and the flow diagram is re-rendered. Co-Authored-By: Claude Opus 5.5 --- doc/CHANGELOG.md | 8 +- doc/architecture.md | 2 +- doc/architecture_flow.puml | 2 +- doc/assets/architecture_flow.svg | 4 +- doc/dev_guide/business_onboarding.md | 6 +- doc/dev_guide/source_layout.md | 2 +- include/adapter/converter_authoring.h | 16 +- include/adapter/io_converter.h | 51 ------ src/adapter/deployment_preparation.cpp | 17 -- src/adapter/deployment_preparation.h | 4 +- src/adapter/input/README.md | 2 +- src/adapter/input/audio_input.cpp | 4 +- src/adapter/input/audit_input.cpp | 5 +- src/adapter/input/doc_query_input.cpp | 7 +- src/adapter/input/image_query_input.cpp | 5 +- src/adapter/input/rerank_input.cpp | 7 +- src/adapter/input/text_input.cpp | 6 +- src/adapter/input/translate_json_input.cpp | 3 +- src/adapter/operator/operator_adapter.cpp | 8 +- src/adapter/output/audio_result_output.cpp | 5 +- src/adapter/output/audit_result_output.cpp | 7 +- src/adapter/output/doc_answer_output.cpp | 9 +- src/adapter/output/invoice_result_output.cpp | 8 +- src/adapter/output/keyword_result_output.cpp | 5 +- src/adapter/output/rerank_result_output.cpp | 4 +- .../output/structured_document_output.cpp | 5 +- .../output/translation_json_output.cpp | 5 +- .../abi/test_adapter_contract_security.cpp | 80 ++++----- tests/contract/abi/test_operator_safety.cpp | 6 +- .../operator/test_operator_api.cpp | 26 ++- tests/support/adapter_harness.h | 34 ++-- tests/unit/adapter/test_adapter_purity.cpp | 165 +++++------------- .../unit/adapter/test_complex_converters.cpp | 60 ++----- .../unit/adapter/test_io_binding_registry.cpp | 12 +- tests/unit/adapter/test_io_converters.cpp | 126 +++++-------- tests/unit/adapter/test_text_converters.cpp | 32 ++-- .../operator/test_operator_value_registry.cpp | 9 +- 37 files changed, 229 insertions(+), 528 deletions(-) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 5ac30435..76242e57 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +转换器回调去掉 `InputPortBindings` / `OutputPortBindings` 参数:`DecodeInputFn` / `EncodeOutputFn`、 +`DecodeRequestRows`、`EncodeResultRows` 与 `ReadOutputValue` 直接使用端口常量读写 `AlgContext`。 +写入未声明端口不再被静默发布到空键名。 + 删除 IoBinding 的端口改名映射(`input_ports` / `output_ports`、`BindIoPort`、`EffectivePortMapping`): 转换器逻辑端口名即业务 Blackboard Key,注册审计直接按端口名核对业务出入口与类型;Catalog 与 `validate-io` 不再输出 `*_port_mapping`。业务接入指南补充端口命名约定。Pipeline JSON 不变。 @@ -31,8 +35,7 @@ Converter 定义精简(不涉及 Operator ABI、Pipeline JSON 与 `.conf`) - Model 继承 `ModelIdentity`、Backend Provider 继承 `BackendIdentity`, 身份只声明一次,Definition 从 `MakeModelDefinition` / `MakeBackendDefinition` 开始; 配置读取使用 `ConfigValueOrDefault`,默认值只写在 `config_fields`。 -- `InputPortBindings` / `OutputPortBindings` 共用 `PortBindings<方向>`;`ResolvedInputLimits` - 更名为 `InputLimits` 且不再出现在部署配置中。 +- `ResolvedInputLimits` 更名为 `InputLimits` 且不再出现在部署配置中。 - 删除 `include/adapter/biz_results.h`、`ModelManager::RegisterModel`(改用 `RegisterBatch`)和 `RuntimeOptions` 中只写不读的 `biz_type`、`depth_num`、`biz_name`;`NodeBase` 的类写法端口辅助 函数移到 `dev_support` 的 `LegacyNodeBase`。 @@ -112,7 +115,6 @@ Pipeline 配置格式保持不变。 声明一次:转换器的 `max_batch_size` 默认改为 0(不设限),有效上限取绑定与两个转换器中正值的 最小值,三者显式为 0 时注册审计和部署准备报错。Binding 现默认使用框架标准批次上限 64, 只有实测确需更小值时才覆盖 `max_batch_size`。 -绑定的 `input_ports` / `output_ports` 可以省略同名映射,需要完整映射的代码改用 `EffectivePortMapping`。 `ValidateDecodeRequest`、`DecodeRequestRows` 删除批次上限参数,改读 Operator 填入的 `InputDecodeOptions::max_batch_size`。Catalog 中生产转换器的 `max_batch_size` 由 64 变为 0;各业务的 有效批次上限、Pipeline 配置格式与公共 Operator ABI 不变。 diff --git a/doc/architecture.md b/doc/architecture.md index 0680c8ac..f1f3614c 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -155,7 +155,7 @@ Demo 不得提前拆解请求或在 SDK 返回后补组业务响应;内部节 - 标准 C++ Operator API(`llm_edgeflow::operator_api`)为唯一公开算法接口,承诺 6 个导出符号(3 个 Operator API 函数与 3 个 AlgBase 日志函数)。Node、Registry、Model、Backend 及第三方运行时符号使用 hidden visibility,不构成稳定动态 ABI。 - 同一 handle 的 `Process` 与 `Control` 串行执行;不同 handle 可并行。`Destroy` 前调用方必须停止提交并等待该 handle 上所有调用返回,释放全部输出指针引用,返回后句柄永久失效。`DeInit` 清理全局登记的所有 handle,调用前须对所有实例完成同样的停流与释放;完整规则见[宿主调用与生命周期](dev_guide/operator_output_allocation.md#宿主调用与生命周期)。 -- C++ Operator API 根据 Key 的最后一个点号解析外部槽位的 `key_suffix`;槽位的 `type_suffix` 再选择 `OperatorValueTypeRegistry` 中的外部 C++ 类型。不同槽位后缀可以复用同一类型。`IoBindingRegistry` 负责将转换器的逻辑端口映射到内部 Pipeline 端口,具体区别见[输出分配方案](dev_guide/operator_output_allocation.md)。 +- C++ Operator API 根据 Key 的最后一个点号解析外部槽位的 `key_suffix`;槽位的 `type_suffix` 再选择 `OperatorValueTypeRegistry` 中的外部 C++ 类型。不同槽位后缀可以复用同一类型。`IoBindingRegistry` 负责关联转换器与业务契约,转换器逻辑端口名即内部 Pipeline 的 Blackboard Key,具体区别见[输出分配方案](dev_guide/operator_output_allocation.md)。 - 组件调用关系:`外部调用方 → Operator → Pipeline → Node → Model → Backend → Platform`。 `Operator` 表达对外交付的算法实例,`Platform`(`ComputePlatform`)表达底层硬件执行平台(CPU、CUDA、AX650、Ascend 等)。 - 同一业务可以使用一个聚合结构槽位,也可以由多个原子槽位组成;支持多槽位解绑。 diff --git a/doc/architecture_flow.puml b/doc/architecture_flow.puml index f2d1b112..82a4721a 100644 --- a/doc/architecture_flow.puml +++ b/doc/architecture_flow.puml @@ -33,7 +33,7 @@ package "创建期:接入准备与统一校验" { artifact "PreparedDeployment\n中性 Pipeline JSON + PipelineIoBoundary" as Prepared component "PipelineValidator\n显式 DAG / 类型端口 / 并发写冲突" as Validator artifact "ValidatedPipelinePlan" as PipelinePlan - artifact "ValidatedIoPlan\nI/O Definition 与端口映射\n输出池规格 + Pipeline 计划" as IoPlan + artifact "ValidatedIoPlan\nConverter 选择与 I/O 边界\n输出池规格 + Pipeline 计划" as IoPlan } package "运行期:C++ Operator SDK" { diff --git a/doc/assets/architecture_flow.svg b/doc/assets/architecture_flow.svg index 7c62a97d..6542fcd7 100644 --- a/doc/assets/architecture_flow.svg +++ b/doc/assets/architecture_flow.svg @@ -1,2 +1,2 @@ - -LLM-EdgeFlow 当前配置与运行流程LLM-EdgeFlow 当前配置与运行流程方案与注册信息创建期:接入准备与统一校验运行期:C++ Operator SDK模型执行(按需)宿主程序 / 统一 DemoCLI / Pipeline Studio.conf仅 pipe_pathPipeline JSON节点 / depends_on / typed ports / modelsdeployment.io / models[].model_pathPipelineCatalog注册 Definition 的查询视图Node Spec + 普通算法函数REGISTER_FUNCTION_NODEModel / Backend / I/O 注册PrepareDeploymentDocument接入绑定 / 路径解析 / 输出分配参数PreparedDeployment中性 Pipeline JSON + PipelineIoBoundaryPipelineValidator显式 DAG / 类型端口 / 并发写冲突ValidatedPipelinePlanValidatedIoPlanI/O Definition 与端口映射输出池规格 + Pipeline 计划OperatorFuncCreate / Process / Control / Destroy命名槽位校验 / 输出租约 / 异常屏障SharedAlgorithmRuntimeInputConverterDefinition::decode_fn完整请求 → 中性输入与元信息OutputConverterDefinition::encode_fn内部结果 → 完整响应与容量检查PipelineBuildFromPlan / Execute / Control消费已验证计划AlgContextBlackboardKey<T> / Read / Publish请求内不可变快照SessionContextModelManager / SessionResourceKey<T>NodeBase → AuthorNode<Spec>通用 Node 与自定义 Nodetyped I/O / 参数 / 模型 / ControlModelRuntimeFactoryModelRegistry + BackendRegistryIModel 能力Embed / Score / Generate / Recognize / Transcribe中性 Backend 会话TensorGraph / TextGeneration / ImageTextGenerationGeneratedTokenEmbedding / AudioTranscriptionFixedBatchExecutorchunk / execution_count / stripModel callback prepares paddingreq_id / sub_id查询提供 Definition编辑 / 保存定位能力与配置契约接入信息NamedIoBatch创建期解析部署创建转移 ValidatedPipelinePlan创建期物化模型Process 校验输入发布Execute依拓扑执行读取 / 发布调用声明能力推理固定批次执行执行成功后编码读取结果与元信息编码完成 / written_count发布输出池租约箭头表示准备、创建或调用关系,不表示编译依赖。可用 Node、Model、Backend 与 Biz 以目标构建 Catalog 为准。 \ No newline at end of file + +LLM-EdgeFlow 当前配置与运行流程LLM-EdgeFlow 当前配置与运行流程方案与注册信息创建期:接入准备与统一校验运行期:C++ Operator SDK模型执行(按需)宿主程序 / 统一 DemoCLI / Pipeline Studio.conf仅 pipe_pathPipeline JSON节点 / depends_on / typed ports / modelsdeployment.io / models[].model_pathPipelineCatalog注册 Definition 的查询视图Node Spec + 普通算法函数REGISTER_FUNCTION_NODEModel / Backend / I/O 注册PrepareDeploymentDocument接入绑定 / 路径解析 / 输出分配参数PreparedDeployment中性 Pipeline JSON + PipelineIoBoundaryPipelineValidator显式 DAG / 类型端口 / 并发写冲突ValidatedPipelinePlanValidatedIoPlanConverter 选择与 I/O 边界输出池规格 + Pipeline 计划OperatorFuncCreate / Process / Control / Destroy命名槽位校验 / 输出租约 / 异常屏障SharedAlgorithmRuntimeInputConverterDefinition::decode_fn完整请求 → 中性输入与元信息OutputConverterDefinition::encode_fn内部结果 → 完整响应与容量检查PipelineBuildFromPlan / Execute / Control消费已验证计划AlgContextBlackboardKey<T> / Read / Publish请求内不可变快照SessionContextModelManager / SessionResourceKey<T>NodeBase → AuthorNode<Spec>通用 Node 与自定义 Nodetyped I/O / 参数 / 模型 / ControlModelRuntimeFactoryModelRegistry + BackendRegistryIModel 能力Embed / Score / Generate / Recognize / Transcribe中性 Backend 会话TensorGraph / TextGeneration / ImageTextGenerationGeneratedTokenEmbedding / AudioTranscriptionFixedBatchExecutorchunk / execution_count / stripModel callback prepares paddingreq_id / sub_id查询提供 Definition编辑 / 保存定位能力与配置契约接入信息NamedIoBatch创建期解析部署创建转移 ValidatedPipelinePlan创建期物化模型Process 校验输入发布Execute依拓扑执行读取 / 发布调用声明能力推理固定批次执行执行成功后编码读取结果与元信息编码完成 / written_count发布输出池租约箭头表示准备、创建或调用关系,不表示编译依赖。可用 Node、Model、Backend 与 Biz 以目标构建 Catalog 为准。 \ No newline at end of file diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 19fc8fb5..520a33da 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -86,7 +86,7 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 | 内部数据边界 | [业务 key](../../include/adapter/biz_blackboard_keys.h)、[中性结果类型](../../include/core/common_contracts.h) | ingress/egress typed key 与 Pipeline 产出的中性结果;已有类型可复用,外部响应由输出转换器组装 | | 输入转换器 | [text_input.cpp](../../src/adapter/input/text_input.cpp) | 外部输入校验、中性数据封装及 `REGISTER_INPUT_CONVERTER` | | 输出转换器 | [keyword_result_output.cpp](../../src/adapter/output/keyword_result_output.cpp) | 内部结果关联、写入已分配的输出结构及 `REGISTER_OUTPUT_CONVERTER` | -| 业务契约与绑定 | [keyword_match_bindings.cpp](../../src/adapter/biz/keyword_match_bindings.cpp) | 声明 `BizDefinition`、转换器组合和非同名端口映射;默认批次上限为 64,用 `REGISTER_IO_BINDING` 注册 | +| 业务契约与绑定 | [keyword_match_bindings.cpp](../../src/adapter/biz/keyword_match_bindings.cpp) | 声明 `BizDefinition` 与转换器组合;默认批次上限为 64,用 `REGISTER_IO_BINDING` 注册 | | Operator 类型注册(仅新宿主类型) | [operator_builtin_value_types.cpp](../../src/adapter/operator/operator_builtin_value_types.cpp) | 复用已注册类型时无需改动;新宿主类型见[实现与注册](operator_output_allocation.md#实现与注册) | | 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` 自动编入;编排业务端口,配置路径和输出容量 | @@ -143,8 +143,8 @@ Operator 的宿主输入校验会拒绝 `CompanyString` 中的原始嵌入 NUL `\u0000` 转义仍可在解包后成为内部字符串的一部分。 共享端口用 `MakeBlackboardKey(name)` 在 `adapter/biz_blackboard_keys.h` 定义一次;转换器 -Definition 使用 `RequiredInputPort(port)` / `OutputPort(port)`,回调通过 `bindings.Key(port)` -读取或发布。转换器端口名就是业务出入口的 Blackboard Key,绑定不做改名。端口命名约定: +Definition 使用 `RequiredInputPort(port)` / `OutputPort(port)`,回调直接用同一端口常量读写 +`AlgContext`。转换器端口名就是业务出入口的 Blackboard Key,绑定不做改名。端口命名约定: - 同一业务内同名即同一份数据、同一类型;复用已定义的常量,不重复手写字符串。 - 可被多个业务复用的转换器使用中性、按角色命名的端口(如 `input_sentences`、`llm_answers`), diff --git a/doc/dev_guide/source_layout.md b/doc/dev_guide/source_layout.md index acd395a6..02b62b68 100644 --- a/doc/dev_guide/source_layout.md +++ b/doc/dev_guide/source_layout.md @@ -86,7 +86,7 @@ src/adapter/ 常见单槽、每请求一行的回调使用 `DecodeRequestRows` / `EncodeResultRows` 调用普通业务函数, 批次与绑定归辅助层;多槽、展开和汇聚保留显式算法。 各业务接入绑定在 `src/adapter/biz/` 中声明 `IoBindingDefinition`,通过 `REGISTER_IO_BINDING` 注册。 -端口 Definition 与回调的 `bindings.Key(port)` 共用 typed 声明,端口名即业务键名, +端口 Definition 与回调共用同一 typed 端口常量,端口名即业务键名, 绑定不做改名。常见必需槽可用 `ExternalInputSlot` / `ExternalOutputSlot` 推导类型和默认同名后缀,输出容量字段由已注册 ValueType 决定; 特殊布局仍使用完整定义。 diff --git a/include/adapter/converter_authoring.h b/include/adapter/converter_authoring.h index 755d650c..5292a5d2 100644 --- a/include/adapter/converter_authoring.h +++ b/include/adapter/converter_authoring.h @@ -113,12 +113,11 @@ inline const std::vector* RequestIds( template inline const T* ReadOutputValue(AlgContext& context, - const OutputPortBindings& bindings, const BlackboardKey& port, const OutputEncodeOptions& options, AdapterStatus* status, const char* field_path = nullptr) { - const auto* value = context.Read(bindings.Key(port)); + const auto* value = context.Read(port); if (!value) { AdapterValidationHelper::ReturnInvalidInput( status, std::string("Missing required context value: ") + port.name, @@ -229,8 +228,7 @@ class OutputStringWriter { template int DecodeRequestRows( const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, - AdapterStatus* status, const char* slot, + AlgContext* context, AdapterStatus* status, const char* slot, const BlackboardKey>>& payload_port, Decode&& decode) { if (!ValidateDecodeRequest(source, options, context, status)) @@ -251,7 +249,7 @@ int DecodeRequestRows( } if (!PublishRequestIds(options, std::move(ids), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(payload_port), std::move(payloads), + *context, payload_port, std::move(payloads), options.converter_id.c_str(), status)) return COMPANY_ALG_ERR_INVALID_INPUT; return COMPANY_ALG_SUCCESS; @@ -261,9 +259,9 @@ int DecodeRequestRows( // 回调负责业务字段与序列化。 template int EncodeResultRows( - AlgContext* context, const OutputPortBindings& bindings, - const OutputEncodeOptions& options, ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status, const char* slot, + AlgContext* context, const OutputEncodeOptions& options, + ExternalOutputBatchView* destination, size_t* written_count, + AdapterStatus* status, const char* slot, const BlackboardKey>>& result_port, Encode&& encode) { if (written_count) *written_count = 0; @@ -272,7 +270,7 @@ int EncodeResultRows( status, "Null AlgContext passed to Encode", "context", options.converter_id.c_str()); const auto* results = - ReadOutputValue(*context, bindings, result_port, options, status, "res"); + ReadOutputValue(*context, result_port, options, status, "res"); if (!results) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* ids = RequestIds(options, status); if (!ids) return COMPANY_ALG_ERR_INVALID_INPUT; diff --git a/include/adapter/io_converter.h b/include/adapter/io_converter.h index a36be045..b2d43fbd 100644 --- a/include/adapter/io_converter.h +++ b/include/adapter/io_converter.h @@ -144,55 +144,6 @@ struct OutputEncodeOptions { const std::vector* request_ids = nullptr; }; -/** - * @brief 逻辑端口到实际 Blackboard Key 的映射助手 - * - * 输入与输出共用查找实现;方向参数使两者保持不同类型,解码只能拿到输入绑定, - * 编码只能拿到输出绑定。 - */ -template -class PortBindings { - public: - PortBindings() = default; - explicit PortBindings(std::unordered_map mapping) - : mapping_(std::move(mapping)) {} - - bool HasKey(const std::string& logical_name) const { - return mapping_.find(logical_name) != mapping_.end(); - } - - template - BlackboardKey Key(const std::string& logical_name) const { - return BlackboardKey{GetActualKey(logical_name).c_str(), - BlackboardTypeTraits::TypeName()}; - } - - // token 表示逻辑端口;实际键始终通过 bindings 解析。 - template - BlackboardKey Key(const BlackboardKey& logical_port) const { - return Key(logical_port.name); - } - - const std::string& GetActualKey(const std::string& logical_name) const { - auto it = mapping_.find(logical_name); - if (it != mapping_.end()) { - return it->second; - } - static const std::string kEmpty; - return kEmpty; - } - - const std::unordered_map& All() const { - return mapping_; - } - - private: - std::unordered_map mapping_; -}; - -using InputPortBindings = PortBindings; -using OutputPortBindings = PortBindings; - /** * @brief 外部槽位定义 (Operator 槽位) */ @@ -236,11 +187,9 @@ inline std::string ExternalType( // 统一输入/输出转换回调函数指针类型 using DecodeInputFn = int (*)(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status); using EncodeOutputFn = int (*)(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status); diff --git a/src/adapter/deployment_preparation.cpp b/src/adapter/deployment_preparation.cpp index 48689471..06630a9b 100644 --- a/src/adapter/deployment_preparation.cpp +++ b/src/adapter/deployment_preparation.cpp @@ -13,19 +13,6 @@ #include "core/pipeline_config.h" namespace llm_edgeflow { -namespace { - -// Converter 的逻辑端口名就是 Blackboard 键。 -std::unordered_map SameNamedKeys( - const std::vector& ports) { - std::unordered_map keys; - for (const auto& port : ports) - keys.emplace(port.logical_name, port.logical_name); - return keys; -} - -} // namespace - bool PrepareDeploymentDocument(const nlohmann::json& document, const DeploymentPrepareOptions& options, PreparedDeployment* output, @@ -244,10 +231,6 @@ bool PrepareDeploymentDocument(const nlohmann::json& document, local_prep.binding = *binding; local_prep.input_converter = in_conv; local_prep.output_converter = out_conv; - local_prep.input_port_bindings = - InputPortBindings(SameNamedKeys(in_conv->logical_ports)); - local_prep.output_port_bindings = - OutputPortBindings(SameNamedKeys(out_conv->logical_ports)); local_prep.effective_max_batch_size = max_batch; local_prep.output_specs = std::move(local_output_specs); local_prep.output_parameter_texts = std::move(local_output_params); diff --git a/src/adapter/deployment_preparation.h b/src/adapter/deployment_preparation.h index 4e3ff4fb..c5150e32 100644 --- a/src/adapter/deployment_preparation.h +++ b/src/adapter/deployment_preparation.h @@ -22,14 +22,12 @@ struct DeploymentPrepareOptions { std::string model_root_dir; }; -// 从部署文档解析出的 Converter 选择、端口映射和输出池预算。 +// 从部署文档解析出的 Converter 选择和输出池预算。 // 由准备阶段生成,经校验的 Operator 计划保持不变。 struct IoBindingSelection { IoBindingDefinition binding; const InputConverterDefinition* input_converter = nullptr; const OutputConverterDefinition* output_converter = nullptr; - InputPortBindings input_port_bindings; - OutputPortBindings output_port_bindings; size_t effective_max_batch_size = 0; std::unordered_map output_specs; diff --git a/src/adapter/input/README.md b/src/adapter/input/README.md index f0c3a642..d3881cc6 100644 --- a/src/adapter/input/README.md +++ b/src/adapter/input/README.md @@ -4,7 +4,7 @@ ## 规范与契约 - 每个输入转换器通过 `REGISTER_INPUT_CONVERTER` 注册 `InputConverterDefinition`。 -- Definition 与回调的 `bindings.Key(port)` 复用同一 typed 端口;普通外部槽用 `ExternalInputSlot`。 +- Definition 与回调复用同一 typed 端口常量;普通外部槽用 `ExternalInputSlot`。 - 单槽、每请求一个载荷使用 `DecodeRequestRows`;业务函数接收宿主值、返回自持有载荷与 `AdapterStatus`,框架处理批次、来源、绑定及错误位置。文本和 PCM 是已编译示例。 - 多槽和候选展开继续显式使用 `ValidateDecodeRequest` / `ReadInputSlot`;批次上限默认采用绑定的框架标准值 64、Operator 经 `InputDecodeOptions` 传入,转换器不另设常量。文本结构检查和复制使用 `IsValidInputString` / `CopyInputString`。 - 输入长度上限引用 `adapter/biz_input_constraints.h`;只有业务确需更严格的限制时,才在转换器中定义具名常量。rerank 候选段落保留 64 KiB 的转换器限制。 diff --git a/src/adapter/input/audio_input.cpp b/src/adapter/input/audio_input.cpp index dcb035cd..34b8cc39 100644 --- a/src/adapter/input/audio_input.cpp +++ b/src/adapter/input/audio_input.cpp @@ -44,11 +44,9 @@ AdapterStatus DecodeAudio(const CompanyOperatorAudioInput& input, int DecodeOperatorAudioInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { return DecodeRequestRows( - source, options, bindings, context, status, kInputSlot, kAudioInputs, - &DecodeAudio); + source, options, context, status, kInputSlot, kAudioInputs, &DecodeAudio); } InputConverterDefinition MakeOperatorAudioInputConverter() { diff --git a/src/adapter/input/audit_input.cpp b/src/adapter/input/audit_input.cpp index df693232..1537d5d5 100644 --- a/src/adapter/input/audit_input.cpp +++ b/src/adapter/input/audit_input.cpp @@ -18,7 +18,6 @@ constexpr const char* kInputSlot = "audit_in"; int DecodeOperatorAuditInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { if (!ValidateDecodeRequest(source, options, context, status)) { return COMPANY_ALG_ERR_INVALID_INPUT; @@ -75,10 +74,10 @@ int DecodeOperatorAuditInput(const ExternalInputBatchView& source, if (!PublishRequestIds(options, std::move(req_ids), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kUserTexts), std::move(user_texts), + *context, kUserTexts, std::move(user_texts), options.converter_id.c_str(), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kChannelNames), std::move(channel_names), + *context, kChannelNames, std::move(channel_names), options.converter_id.c_str(), status)) { return COMPANY_ALG_ERR_INVALID_INPUT; } diff --git a/src/adapter/input/doc_query_input.cpp b/src/adapter/input/doc_query_input.cpp index 4145091e..668dd34a 100644 --- a/src/adapter/input/doc_query_input.cpp +++ b/src/adapter/input/doc_query_input.cpp @@ -18,7 +18,6 @@ constexpr const char* kInputSlot = "doc_in"; int DecodeOperatorDocQueryInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { if (!ValidateDecodeRequest(source, options, context, status)) { return COMPANY_ALG_ERR_INVALID_INPUT; @@ -74,10 +73,10 @@ int DecodeOperatorDocQueryInput(const ExternalInputBatchView& source, if (!PublishRequestIds(options, std::move(raw_req_ids), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kRawDocs), std::move(raw_docs), - options.converter_id.c_str(), status) || + *context, kRawDocs, std::move(raw_docs), options.converter_id.c_str(), + status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kRawQueries), std::move(raw_queries), + *context, kRawQueries, std::move(raw_queries), options.converter_id.c_str(), status)) { return COMPANY_ALG_ERR_INVALID_INPUT; } diff --git a/src/adapter/input/image_query_input.cpp b/src/adapter/input/image_query_input.cpp index 8c9d8543..bab7fed6 100644 --- a/src/adapter/input/image_query_input.cpp +++ b/src/adapter/input/image_query_input.cpp @@ -19,7 +19,6 @@ constexpr const char* kQuerySlot = "string"; int DecodeOperatorImageQueryInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { if (!ValidateDecodeRequest(source, options, context, status)) { return COMPANY_ALG_ERR_INVALID_INPUT; @@ -75,10 +74,10 @@ int DecodeOperatorImageQueryInput(const ExternalInputBatchView& source, if (!PublishRequestIds(options, std::move(raw_req_ids), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kImagePaths), std::move(raw_images), + *context, kImagePaths, std::move(raw_images), options.converter_id.c_str(), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kUserQueries), std::move(raw_queries), + *context, kUserQueries, std::move(raw_queries), options.converter_id.c_str(), status)) { return COMPANY_ALG_ERR_INVALID_INPUT; } diff --git a/src/adapter/input/rerank_input.cpp b/src/adapter/input/rerank_input.cpp index 2e786598..b6a403bc 100644 --- a/src/adapter/input/rerank_input.cpp +++ b/src/adapter/input/rerank_input.cpp @@ -23,7 +23,6 @@ constexpr size_t kMaxCandidatePassageBytes = biz_input::kMaxTextBytes; int DecodeOperatorRerankInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { if (!ValidateDecodeRequest(source, options, context, status)) { return COMPANY_ALG_ERR_INVALID_INPUT; @@ -94,13 +93,13 @@ int DecodeOperatorRerankInput(const ExternalInputBatchView& source, if (!PublishRequestIds(options, std::move(raw_req_ids), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kRerankQueries), std::move(queries), + *context, kRerankQueries, std::move(queries), options.converter_id.c_str(), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kRerankCandidates), std::move(candidates), + *context, kRerankCandidates, std::move(candidates), options.converter_id.c_str(), status) || !AdapterValidationHelper::PublishContextValue( - *context, bindings.Key(kRerankPairs), std::move(pairs), + *context, kRerankPairs, std::move(pairs), options.converter_id.c_str(), status)) { return COMPANY_ALG_ERR_INVALID_INPUT; } diff --git a/src/adapter/input/text_input.cpp b/src/adapter/input/text_input.cpp index 3a1b7140..76534883 100644 --- a/src/adapter/input/text_input.cpp +++ b/src/adapter/input/text_input.cpp @@ -34,19 +34,17 @@ AdapterStatus DecodeSentence(const Host& input, std::string* text) { int DecodeOperatorEntityInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { return DecodeRequestRows( - source, options, bindings, context, status, kEntitySlot, kInputSentences, + source, options, context, status, kEntitySlot, kInputSentences, &DecodeSentence); } int DecodeOperatorKeywordInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { return DecodeRequestRows( - source, options, bindings, context, status, kKeywordSlot, kInputSentences, + source, options, context, status, kKeywordSlot, kInputSentences, &DecodeSentence); } diff --git a/src/adapter/input/translate_json_input.cpp b/src/adapter/input/translate_json_input.cpp index 9675ee40..58ad0874 100644 --- a/src/adapter/input/translate_json_input.cpp +++ b/src/adapter/input/translate_json_input.cpp @@ -49,10 +49,9 @@ AdapterStatus DecodeTranslateQuery(const CompanyOperatorEntityInput& input, int DecodeOperatorTranslateJson(const ExternalInputBatchView& source, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, AdapterStatus* status) { return DecodeRequestRows( - source, options, bindings, context, status, kInputSlot, kInputSentences, + source, options, context, status, kInputSlot, kInputSentences, &DecodeTranslateQuery); } diff --git a/src/adapter/operator/operator_adapter.cpp b/src/adapter/operator/operator_adapter.cpp index 61cbfa4f..878dde8e 100644 --- a/src/adapter/operator/operator_adapter.cpp +++ b/src/adapter/operator/operator_adapter.cpp @@ -356,9 +356,8 @@ int Operator_Process(void* handle, const NamedIoBatch& inputs, in_options.request_ids = &request_ids; llm_edgeflow::AdapterStatus decode_status; - int decode_ret = input_converter->decode_fn(in_view, in_options, - plan->input_port_bindings, - &req_ctx, &decode_status); + int decode_ret = input_converter->decode_fn(in_view, in_options, &req_ctx, + &decode_status); if (decode_ret != 0) { SetLastError("DecodeInput failed for " + input_converter->converter_id + ": " + decode_status.ToString()); @@ -417,8 +416,7 @@ int Operator_Process(void* handle, const NamedIoBatch& inputs, size_t written_count = 0; llm_edgeflow::AdapterStatus encode_status; int encode_ret = output_converter->encode_fn( - &req_ctx, plan->output_port_bindings, out_options, &out_view, - &written_count, &encode_status); + &req_ctx, out_options, &out_view, &written_count, &encode_status); if (encode_ret != 0) { SetLastError("EncodeOutput failed for " + output_converter->converter_id + ": " + encode_status.ToString()); diff --git a/src/adapter/output/audio_result_output.cpp b/src/adapter/output/audio_result_output.cpp index a2e08968..db3b7bba 100644 --- a/src/adapter/output/audio_result_output.cpp +++ b/src/adapter/output/audio_result_output.cpp @@ -18,7 +18,6 @@ namespace { constexpr const char* kOutputSlot = "audio_out"; int EncodeOperatorAudioResult(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { @@ -29,11 +28,11 @@ int EncodeOperatorAudioResult(AlgContext* context, } const auto* transcripts = - ReadOutputValue(*context, bindings, kTranscripts, options, status); + ReadOutputValue(*context, kTranscripts, options, status); if (!transcripts) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* intent_slots = - ReadOutputValue(*context, bindings, kIntentSlots, options, status); + ReadOutputValue(*context, kIntentSlots, options, status); if (!intent_slots) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); diff --git a/src/adapter/output/audit_result_output.cpp b/src/adapter/output/audit_result_output.cpp index 70f86005..1a306dc0 100644 --- a/src/adapter/output/audit_result_output.cpp +++ b/src/adapter/output/audit_result_output.cpp @@ -19,7 +19,6 @@ namespace { constexpr const char* kOutputSlot = "audit_out"; int EncodeOperatorAuditResult(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { @@ -29,12 +28,12 @@ int EncodeOperatorAuditResult(AlgContext* context, options.converter_id.c_str()); } - const auto* verdicts = ReadOutputValue( - *context, bindings, kStructuredVerdicts, options, status, "verdicts"); + const auto* verdicts = ReadOutputValue(*context, kStructuredVerdicts, options, + status, "verdicts"); if (!verdicts) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* matched_policy = - ReadOutputValue(*context, bindings, kMatchedPolicy, options, status); + ReadOutputValue(*context, kMatchedPolicy, options, status); if (!matched_policy) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); diff --git a/src/adapter/output/doc_answer_output.cpp b/src/adapter/output/doc_answer_output.cpp index c15367f5..8be0c373 100644 --- a/src/adapter/output/doc_answer_output.cpp +++ b/src/adapter/output/doc_answer_output.cpp @@ -17,7 +17,6 @@ namespace { constexpr const char* kOutputSlot = "doc_out"; int EncodeOperatorDocAnswer(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { @@ -27,19 +26,19 @@ int EncodeOperatorDocAnswer(AlgContext* context, options.converter_id.c_str()); } - const auto* answers = ReadOutputValue(*context, bindings, kLlmAnswers, - options, status, "answers"); + const auto* answers = + ReadOutputValue(*context, kLlmAnswers, options, status, "answers"); if (!answers) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); if (!raw_req_ids) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* intent_matches = - ReadOutputValue(*context, bindings, kIntentMatches, options, status); + ReadOutputValue(*context, kIntentMatches, options, status); if (!intent_matches) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* chunk_counts = - ReadOutputValue(*context, bindings, kDocChunkCounts, options, status); + ReadOutputValue(*context, kDocChunkCounts, options, status); if (!chunk_counts) return COMPANY_ALG_ERR_INVALID_INPUT; size_t count = answers->size(); diff --git a/src/adapter/output/invoice_result_output.cpp b/src/adapter/output/invoice_result_output.cpp index 42259fbb..629c2db0 100644 --- a/src/adapter/output/invoice_result_output.cpp +++ b/src/adapter/output/invoice_result_output.cpp @@ -17,7 +17,6 @@ namespace { constexpr const char* kOutputSlot = "od_out"; int EncodeOperatorInvoiceResult(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { @@ -27,12 +26,11 @@ int EncodeOperatorInvoiceResult(AlgContext* context, options.converter_id.c_str()); } - const auto* invoice_jsons = ReadOutputValue( - *context, bindings, kExtractedInvoiceJson, options, status); + const auto* invoice_jsons = + ReadOutputValue(*context, kExtractedInvoiceJson, options, status); if (!invoice_jsons) return COMPANY_ALG_ERR_INVALID_INPUT; - const auto* ocr_docs = - ReadOutputValue(*context, bindings, kOcrDocs, options, status); + const auto* ocr_docs = ReadOutputValue(*context, kOcrDocs, options, status); if (!ocr_docs) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); diff --git a/src/adapter/output/keyword_result_output.cpp b/src/adapter/output/keyword_result_output.cpp index 287a5d0c..a93ab121 100644 --- a/src/adapter/output/keyword_result_output.cpp +++ b/src/adapter/output/keyword_result_output.cpp @@ -26,13 +26,12 @@ AdapterStatus EncodeKeyword(const RuleMatchItem& result, } int EncodeOperatorKeywordResult(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { return EncodeResultRows( - context, bindings, options, destination, written_count, status, - kOutputSlot, kRuleMatches, &EncodeKeyword); + context, options, destination, written_count, status, kOutputSlot, + kRuleMatches, &EncodeKeyword); } OutputConverterDefinition MakeOperatorKeywordResultOutputConverter() { diff --git a/src/adapter/output/rerank_result_output.cpp b/src/adapter/output/rerank_result_output.cpp index 06d284e5..90f21f5f 100644 --- a/src/adapter/output/rerank_result_output.cpp +++ b/src/adapter/output/rerank_result_output.cpp @@ -20,7 +20,6 @@ namespace { constexpr const char* kOutputSlot = "rerank_out"; int EncodeOperatorRerankResult(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { @@ -30,8 +29,7 @@ int EncodeOperatorRerankResult(AlgContext* context, options.converter_id.c_str()); } - const auto* res = - ReadOutputValue(*context, bindings, kRankedResults, options, status); + const auto* res = ReadOutputValue(*context, kRankedResults, options, status); if (!res) return COMPANY_ALG_ERR_INVALID_INPUT; const auto* raw_req_ids = RequestIds(options, status); diff --git a/src/adapter/output/structured_document_output.cpp b/src/adapter/output/structured_document_output.cpp index 83226b25..bd996e2f 100644 --- a/src/adapter/output/structured_document_output.cpp +++ b/src/adapter/output/structured_document_output.cpp @@ -28,14 +28,13 @@ AdapterStatus EncodeDocument(const JsonDocumentItem& result, } int EncodeOperatorStructuredDocument(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { return EncodeResultRows( - context, bindings, options, destination, written_count, status, - kOutputSlot, kExtractedEntities, &EncodeDocument); + context, options, destination, written_count, status, kOutputSlot, + kExtractedEntities, &EncodeDocument); } OutputConverterDefinition MakeOperatorStructuredDocumentOutputConverter() { diff --git a/src/adapter/output/translation_json_output.cpp b/src/adapter/output/translation_json_output.cpp index fa3e13b7..f1a10472 100644 --- a/src/adapter/output/translation_json_output.cpp +++ b/src/adapter/output/translation_json_output.cpp @@ -26,14 +26,13 @@ AdapterStatus EncodeTranslation(const std::string& result, } int EncodeOperatorTranslationJson(AlgContext* context, - const OutputPortBindings& bindings, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { return EncodeResultRows( - context, bindings, options, destination, written_count, status, - kOutputSlot, kLlmAnswers, &EncodeTranslation); + context, options, destination, written_count, status, kOutputSlot, + kLlmAnswers, &EncodeTranslation); } OutputConverterDefinition MakeOperatorTranslationJsonOutputConverter() { diff --git a/tests/contract/abi/test_adapter_contract_security.cpp b/tests/contract/abi/test_adapter_contract_security.cpp index b17c7c0c..0bd77dbc 100644 --- a/tests/contract/abi/test_adapter_contract_security.cpp +++ b/tests/contract/abi/test_adapter_contract_security.cpp @@ -311,7 +311,6 @@ TEST_F(AdapterContractSecurityTest, AlgContext large; std::vector request_ids{123}; large.Publish(kLlmAnswers, TextBatch{{0, 0, translation}}); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.converter_id = "translate.json.operator.v1"; options.request_ids = &request_ids; @@ -330,9 +329,9 @@ TEST_F(AdapterContractSecurityTest, fixed_view.SetCapacity("entity_out", "entities_json", 2047); size_t written = 0; - EXPECT_EQ(converter->encode_fn(&large, bindings, options, &fixed_view, - &written, &status), - COMPANY_ALG_ERR_BUFFER_TOO_SMALL); + EXPECT_EQ( + converter->encode_fn(&large, options, &fixed_view, &written, &status), + COMPANY_ALG_ERR_BUFFER_TOO_SMALL); // 重排后的内部结果必须映射回外部请求 ID。 AlgContext reordered; @@ -352,8 +351,8 @@ TEST_F(AdapterContractSecurityTest, reordered_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; reordered_view.SetCapacity("entity_out", "entities_json", 511); - ASSERT_EQ(converter->encode_fn(&reordered, bindings, options, &reordered_view, - &written, &status), + ASSERT_EQ(converter->encode_fn(&reordered, options, &reordered_view, &written, + &status), 0); EXPECT_EQ(written, 2U); EXPECT_EQ(first.request_id, 999U); @@ -364,8 +363,8 @@ TEST_F(AdapterContractSecurityTest, nlohmann::json({{"translated", "第二句"}})); reordered_view.count = 1; - EXPECT_EQ(converter->encode_fn(&reordered, bindings, options, &reordered_view, - &written, &status), + EXPECT_EQ(converter->encode_fn(&reordered, options, &reordered_view, &written, + &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); for (const TextBatch& invalid : @@ -377,9 +376,9 @@ TEST_F(AdapterContractSecurityTest, AlgContext ctx; ctx.Publish(kLlmAnswers, invalid); reordered_view.count = 2; - EXPECT_EQ(converter->encode_fn(&ctx, bindings, options, &reordered_view, - &written, &status), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ( + converter->encode_fn(&ctx, options, &reordered_view, &written, &status), + COMPANY_ALG_ERR_INVALID_INPUT); } for (bool publish_ids : {false, true}) { AlgContext missing; @@ -390,8 +389,8 @@ TEST_F(AdapterContractSecurityTest, missing.Publish(kLlmAnswers, TextBatch{{0, 0, "你好"}}); } reordered_view.count = 1; - EXPECT_EQ(converter->encode_fn(&missing, bindings, options, &reordered_view, - &written, &status), + EXPECT_EQ(converter->encode_fn(&missing, options, &reordered_view, &written, + &status), COMPANY_ALG_ERR_INVALID_INPUT); } } @@ -698,7 +697,6 @@ TEST_F(AdapterContractSecurityTest, DirectUnpackMemoryIsolation) { in_view.count = 1; in_view.slots["keyword_in"] = llm_edgeflow::BorrowInputForTest({&in_struct}); in_view.slot_types["keyword_in"] = "CompanyOperatorKeywordInput"; - InputPortBindings in_bindings({{"input_sentences", "input_sentences"}}); InputDecodeOptions in_options; in_options.converter_id = "keyword.plain.operator.v1"; std::vector request_ids; @@ -706,8 +704,7 @@ TEST_F(AdapterContractSecurityTest, DirectUnpackMemoryIsolation) { AlgContext ctx; AdapterStatus status; - int unpack_ret = - input_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int unpack_ret = input_conv->decode_fn(in_view, in_options, &ctx, &status); ASSERT_EQ(unpack_ret, COMPANY_ALG_SUCCESS); // 立即篡改调用方内存 Buffer (例如 memset 覆盖为 'X') @@ -895,13 +892,12 @@ TEST_F(AdapterContractSecurityTest, llm_edgeflow::BorrowInputForTest({&in_carrier}); carrier_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - InputPortBindings bindings({{"input_sentences", "input_sentences"}}); InputDecodeOptions options; options.converter_id = "translate.json.operator.v1"; AlgContext carrier_ctx; AdapterStatus carrier_status; - int ret = converter->decode_fn(carrier_view, options, bindings, &carrier_ctx, + int ret = converter->decode_fn(carrier_view, options, &carrier_ctx, &carrier_status); EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(carrier_status.SampleIndex(), 0); @@ -919,9 +915,8 @@ TEST_F(AdapterContractSecurityTest, AlgContext biz_ctx; AdapterStatus biz_status; - EXPECT_EQ( - converter->decode_fn(biz_view, options, bindings, &biz_ctx, &biz_status), - COMPANY_ALG_ERR_INVALID_INPUT); + 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.FieldPath(), "json"); } @@ -943,15 +938,13 @@ TEST_F(AdapterContractSecurityTest, view.leased_slots["entity_out"] = {&out}; view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; - OutputPortBindings bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.converter_id = "translate.json.operator.v1"; options.request_ids = &request_ids; size_t written = 0; AdapterStatus status; - int ret = - converter->encode_fn(&ctx, bindings, options, &view, &written, &status); + int ret = converter->encode_fn(&ctx, options, &view, &written, &status); // 缺少内部数据属于非法输入,与输出容量无关。 EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); @@ -977,7 +970,6 @@ TEST_F(AdapterContractSecurityTest, view.leased_slots["entity_out"] = {&out}; view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; - OutputPortBindings bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.converter_id = "translate.json.operator.v1"; options.request_ids = &request_ids; @@ -987,9 +979,8 @@ TEST_F(AdapterContractSecurityTest, // Encode 中先序列化再校验容量; // 未处理的 dump 异常会从 encode_fn 抛出 - EXPECT_THROW( - converter->encode_fn(&ctx, bindings, options, &view, &written, &status), - std::exception); + EXPECT_THROW(converter->encode_fn(&ctx, options, &view, &written, &status), + std::exception); } // 翻译在 AlgContext 为空时的诊断 @@ -1011,21 +1002,19 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { in_view.slots["entity_in"] = llm_edgeflow::BorrowInputForTest({&input}); in_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - InputPortBindings in_bindings({{"input_sentences", "input_sentences"}}); InputDecodeOptions in_options; in_options.converter_id = "translate.json.operator.v1"; AdapterStatus unpack_status; - int unpack_ret = in_conv->decode_fn(in_view, in_options, in_bindings, nullptr, - &unpack_status); + int unpack_ret = + in_conv->decode_fn(in_view, in_options, nullptr, &unpack_status); 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( - in_conv->decode_fn(in_view, in_options, in_bindings, nullptr, nullptr), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ(in_conv->decode_fn(in_view, in_options, nullptr, nullptr), + COMPANY_ALG_ERR_INVALID_INPUT); // 2. context 为空时 Encode 必须返回 INVALID_INPUT (-3),字段为 "context" CompanyOperatorEntityOutput output{}; @@ -1034,22 +1023,21 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { out_view.leased_slots["entity_out"] = {&output}; out_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; - OutputPortBindings out_bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions out_options; out_options.converter_id = "translate.json.operator.v1"; size_t written = 0; AdapterStatus pack_status; - int pack_ret = out_conv->encode_fn(nullptr, out_bindings, out_options, - &out_view, &written, &pack_status); + int pack_ret = out_conv->encode_fn(nullptr, out_options, &out_view, &written, + &pack_status); 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(out_conv->encode_fn(nullptr, out_bindings, out_options, &out_view, - &written, nullptr), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ( + out_conv->encode_fn(nullptr, out_options, &out_view, &written, nullptr), + COMPANY_ALG_ERR_INVALID_INPUT); } TEST_F(AdapterContractSecurityTest, @@ -1071,10 +1059,7 @@ TEST_F(AdapterContractSecurityTest, in_options.converter_id = input->converter_id; AlgContext context; AdapterStatus status; - EXPECT_EQ(input->decode_fn( - source, in_options, - InputPortBindings({{"input_sentences", "input_sentences"}}), - &context, &status), + EXPECT_EQ(input->decode_fn(source, in_options, &context, &status), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.FieldPath(), "request_ids"); EXPECT_EQ(status.AdapterName(), input->converter_id); @@ -1092,9 +1077,7 @@ TEST_F(AdapterContractSecurityTest, destination.slot_types["keyword_out"] = "CompanyOperatorKeywordOutput"; size_t written = 99; EXPECT_EQ( - output->encode_fn(&context, - OutputPortBindings({{"rule_matches", "rule_matches"}}), - out_options, &destination, &written, &status), + output->encode_fn(&context, out_options, &destination, &written, &status), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.FieldPath(), "request_ids"); EXPECT_EQ(status.AdapterName(), output->converter_id); @@ -1196,8 +1179,7 @@ TEST_F(AdapterContractSecurityTest, InputLengthLimitsStayUnchanged) { options.converter_id = id; std::vector request_ids; options.request_ids = &request_ids; - const int result = converter->decode_fn( - view, options, InputPortBindings(mapping), &context, &status); + const int result = converter->decode_fn(view, options, &context, &status); EXPECT_EQ(result, extra == 0 ? 0 : COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.ToString(), extra == 0 ? "OK" diff --git a/tests/contract/abi/test_operator_safety.cpp b/tests/contract/abi/test_operator_safety.cpp index 93e270f2..d052a370 100644 --- a/tests/contract/abi/test_operator_safety.cpp +++ b/tests/contract/abi/test_operator_safety.cpp @@ -641,12 +641,10 @@ TEST_F(OperatorSafetyTest, EntityFailureSampleSentinelValues) { options.converter_id = out_conv->converter_id; options.request_ids = &request_ids; - llm_edgeflow::OutputPortBindings bindings( - {{"extracted_entities", "extracted_entities"}}); size_t written_count = 0; llm_edgeflow::AdapterStatus status; - int ret = out_conv->encode_fn(&ctx, bindings, options, &out_view, - &written_count, &status); + int ret = + out_conv->encode_fn(&ctx, options, &out_view, &written_count, &status); EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(out0.request_id, 1001u); diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index ccf49f5f..8b1babee 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -2378,15 +2378,13 @@ void RegisterNestedOutputTestTypes() { REGISTER_OPERATOR_VALUE_TYPE(RegisterNestedOutputTestTypes); -int EncodeNestedOutput(AlgContext* context, const OutputPortBindings& bindings, - const OutputEncodeOptions& options, +int EncodeNestedOutput(AlgContext* context, const OutputEncodeOptions& options, ExternalOutputBatchView* destination, size_t* written_count, AdapterStatus* status) { if (written_count) *written_count = 0; if (!context || !destination) return -1; const auto* req_ids = RequestIds(options, status); - const auto* matches = - context->Read(bindings.GetActualKey("rule_matches")); + const auto* matches = context->Read(kRuleMatches); if (!req_ids || !matches) return -3; size_t count = req_ids->size(); @@ -2525,13 +2523,11 @@ TEST_F(OperatorApiTest, ProcessRejectsConverterRecordingWrongRequestIdCount) { auto input = *production; input.converter_id = "test_partial_request_ids.operator.v1"; input.decode_fn = [](const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, + const InputDecodeOptions& options, AlgContext* context, AdapterStatus* status) { const auto* converter = IoConverterRegistry::Instance().FindInputConverter( "keyword.plain.operator.v1"); - const int ret = - converter->decode_fn(source, options, bindings, context, status); + const int ret = converter->decode_fn(source, options, context, status); if (ret == 0) options.request_ids->resize(1); return ret; }; @@ -2944,16 +2940,14 @@ TEST_F(OperatorApiTest, SharedCarrierDoesNotMergePayloadSchema) { view_plain.slots["entity_in"] = llm_edgeflow::BorrowInputForTest({&c_in_plain}); view_plain.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - llm_edgeflow::InputPortBindings port_bindings( - {{"input_sentences", "input_sentences"}}); llm_edgeflow::InputDecodeOptions decode_opts; decode_opts.converter_id = translate_in_conv->converter_id; std::vector request_ids; decode_opts.request_ids = &request_ids; - EXPECT_EQ(translate_in_conv->decode_fn(view_plain, decode_opts, port_bindings, - &ctx, &status), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ( + translate_in_conv->decode_fn(view_plain, decode_opts, &ctx, &status), + COMPANY_ALG_ERR_INVALID_INPUT); // 2. JSON 文本:翻译接受并提取 "query" std::string json_text = "{\"query\":\"有效翻译查询\"}"; @@ -2965,9 +2959,9 @@ TEST_F(OperatorApiTest, SharedCarrierDoesNotMergePayloadSchema) { view_json.count = 1; view_json.slots["entity_in"] = llm_edgeflow::BorrowInputForTest({&c_in_json}); view_json.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - EXPECT_EQ(translate_in_conv->decode_fn(view_json, decode_opts, port_bindings, - &valid_ctx, &status), - COMPANY_ALG_SUCCESS); + EXPECT_EQ( + translate_in_conv->decode_fn(view_json, decode_opts, &valid_ctx, &status), + COMPANY_ALG_SUCCESS); const auto* queries = valid_ctx.Read(llm_edgeflow::kInputSentences); ASSERT_NE(queries, nullptr); EXPECT_EQ((*queries)[0].data, "有效翻译查询"); diff --git a/tests/support/adapter_harness.h b/tests/support/adapter_harness.h index 70d5bcce..2fdc645c 100644 --- a/tests/support/adapter_harness.h +++ b/tests/support/adapter_harness.h @@ -25,25 +25,14 @@ namespace test { class AdapterHarness { public: AdapterHarness(const InputConverterDefinition* input_conv, - const OutputConverterDefinition* output_conv, - InputPortBindings in_bindings = {}, - OutputPortBindings out_bindings = {}) - : in_conv_(input_conv), - out_conv_(output_conv), - in_bindings_(std::move(in_bindings)), - out_bindings_(std::move(out_bindings)) {} - - explicit AdapterHarness(const InputConverterDefinition* input_conv, - InputPortBindings in_bindings = {}) - : in_conv_(input_conv), - out_conv_(nullptr), - in_bindings_(std::move(in_bindings)) {} - - explicit AdapterHarness(const OutputConverterDefinition* output_conv, - OutputPortBindings out_bindings = {}) - : in_conv_(nullptr), - out_conv_(output_conv), - out_bindings_(std::move(out_bindings)) {} + const OutputConverterDefinition* output_conv) + : in_conv_(input_conv), out_conv_(output_conv) {} + + explicit AdapterHarness(const InputConverterDefinition* input_conv) + : in_conv_(input_conv), out_conv_(nullptr) {} + + explicit AdapterHarness(const OutputConverterDefinition* output_conv) + : in_conv_(nullptr), out_conv_(output_conv) {} AlgContext& Context() { return ctx_; } const AlgContext& Context() const { return ctx_; } @@ -76,7 +65,7 @@ class AdapterHarness { options.converter_id = in_conv_->converter_id; options.request_ids = &request_ids_; - return in_conv_->decode_fn(view, options, in_bindings_, &ctx_, &status_); + return in_conv_->decode_fn(view, options, &ctx_, &status_); } template @@ -114,8 +103,7 @@ class AdapterHarness { OutputEncodeOptions options; options.converter_id = out_conv_->converter_id; options.request_ids = &request_ids_; - return out_conv_->encode_fn(&ctx_, out_bindings_, options, view, - written_count, &status_); + return out_conv_->encode_fn(&ctx_, options, view, written_count, &status_); } template @@ -175,8 +163,6 @@ class AdapterHarness { private: const InputConverterDefinition* in_conv_ = nullptr; const OutputConverterDefinition* out_conv_ = nullptr; - InputPortBindings in_bindings_; - OutputPortBindings out_bindings_; AlgContext ctx_; AdapterStatus status_; std::vector request_ids_; diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 582b9477..38fb7ffb 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -144,13 +144,7 @@ TEST_F(AdapterPurityTest, DocQaAdapterPurity) { "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings( - {{"raw_docs", "raw_docs"}, {"raw_queries", "raw_queries"}}), - OutputPortBindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string doc_str = "Doc Content"; std::string query_str = "Query Question"; @@ -205,10 +199,7 @@ TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { "keyword.result.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"input_sentences", "input_sentences"}}), - OutputPortBindings({{"rule_matches", "rule_matches"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string text_str = "Some text"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -239,10 +230,7 @@ TEST_F(AdapterPurityTest, EntityExtractAdapterPurity) { "document.structured.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"input_sentences", "input_sentences"}}), - OutputPortBindings({{"extracted_entities", "extracted_entities"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string text_str = "Entity text"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -272,12 +260,7 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapterPurity) { "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings( - {{"user_texts", "user_texts"}, {"channel_names", "channel_names"}}), - OutputPortBindings({{"structured_verdicts", "structured_verdicts"}, - {"matched_policy", "matched_policy"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string text_str = "audit sentence"; std::string chan_str = "channel_vip"; @@ -332,8 +315,6 @@ TEST_F(AdapterPurityTest, OcrInvoiceQaAdapterPurity) { in_view.slot_types["frame"] = "CompanyFrame"; in_view.slot_types["string"] = "CompanyString"; - InputPortBindings in_bindings( - {{"image_paths", "image_paths"}, {"user_queries", "user_queries"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -341,8 +322,7 @@ TEST_F(AdapterPurityTest, OcrInvoiceQaAdapterPurity) { AlgContext ctx; AdapterStatus status; - ASSERT_EQ(in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status), - 0); + ASSERT_EQ(in_conv->decode_fn(in_view, in_options, &ctx, &status), 0); StructuredDocumentBatch invoices; invoices.emplace_back( @@ -362,17 +342,13 @@ TEST_F(AdapterPurityTest, OcrInvoiceQaAdapterPurity) { out_view.slot_types["od_out"] = "CompanyOdOutput"; out_view.SetCapacity("od_out", "result_json", sizeof(od_fix.json) - 1); - OutputPortBindings out_bindings( - {{"extracted_invoice_json", "extracted_invoice_json"}, - {"ocr_docs", "ocr_docs"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ASSERT_EQ(out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status), - 0); + ASSERT_EQ( + out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status), 0); EXPECT_EQ(od_fix.out.request_id, 1005u); EXPECT_EQ(od_fix.out.detected_box_count, 1); @@ -388,10 +364,7 @@ TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { "audio_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, InputPortBindings({{"audio_inputs", "audio_inputs"}}), - OutputPortBindings( - {{"transcripts", "transcripts"}, {"intent_slots", "intent_slots"}})); + test::AdapterHarness harness(in_conv, out_conv); std::vector pcm(1600, 0.05f); CompanyOperatorAudioInput in{1006, pcm.data(), static_cast(pcm.size()), @@ -427,12 +400,7 @@ TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { "rerank_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"rerank_queries", "rerank_queries"}, - {"rerank_candidates", "rerank_candidates"}, - {"rerank_pairs", "rerank_pairs"}}), - OutputPortBindings({{"ranked_results", "ranked_results"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string q_str = "query"; std::string cand0_str = "cand0"; @@ -476,10 +444,7 @@ TEST_F(AdapterPurityTest, TranslateAdapterPurity) { "translate.json.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"input_sentences", "input_sentences"}}), - OutputPortBindings({{"llm_answers", "llm_answers"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string json_query = "{\"query\":\"Hello\"}"; CompanyString cs_text{static_cast(json_query.size()), @@ -511,13 +476,9 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - OutputPortBindings out_bindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}); - // 情形 1:缺少 llm_answers { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{1001}); DocOutputFixture fix; std::vector outputs = {fix.out}; @@ -526,7 +487,7 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { // 情形 2:有 llm_answers 但缺少 intent_matches -> 必须 fail-closed { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{1001}); TextBatch answers; answers.emplace_back(0, 0, "Some answer"); @@ -539,7 +500,7 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { // 情形 3:有 intent_matches 但缺少显式分块计数 -> 必须 fail-closed { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{1001}); TextBatch answers; answers.emplace_back(0, 0, "Some answer"); @@ -555,7 +516,7 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { // 情形 4:所有输出都存在,但缺少请求 ID 表 { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); TextBatch answers; answers.emplace_back(0, 0, "Some answer"); harness.Publish("llm_answers", std::move(answers)); @@ -578,8 +539,8 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { options.converter_id = out_conv->converter_id; AdapterStatus status; size_t written = 0; - EXPECT_EQ(out_conv->encode_fn(&harness.Context(), out_bindings, options, - &destination, &written, &status), + EXPECT_EQ(out_conv->encode_fn(&harness.Context(), options, &destination, + &written, &status), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.FieldPath(), "request_ids"); } @@ -591,11 +552,7 @@ TEST_F(AdapterPurityTest, "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - OutputPortBindings out_bindings( - {{"structured_verdicts", "structured_verdicts"}, - {"matched_policy", "matched_policy"}}); - - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{1001}); // structured_verdicts 缺少必填字段 'risk_level' -> 必须 fail-closed @@ -621,14 +578,10 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - OutputPortBindings out_bindings( - {{"structured_verdicts", "structured_verdicts"}, - {"matched_policy", "matched_policy"}}); - for (const auto parse_status : {JsonParseStatus::kOk, JsonParseStatus::kFailed, JsonParseStatus::kFallbackApplied}) { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{100, 200}); StructuredDocumentBatch verdicts; @@ -663,11 +616,9 @@ TEST_F(AdapterPurityTest, OneToOneResultsRejectDuplicateAndOutOfRangeIds) { "keyword.result.operator.v1"); ASSERT_NE(out_conv, nullptr); - OutputPortBindings out_bindings({{"rule_matches", "rule_matches"}}); - for (const auto& ids : {std::vector{0, 0}, std::vector{0, 2}}) { - test::AdapterHarness harness(out_conv, out_bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{100, 200}); RuleMatchBatch matches; for (auto id : ids) matches.emplace_back(id, 0, RuleMatchItem{}); @@ -685,9 +636,6 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapter_RejectsOversizedChannelName) { "audit.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); - InputPortBindings in_bindings( - {{"user_texts", "user_texts"}, {"channel_names", "channel_names"}}); - const std::string valid_channel(256, 'c'); const std::string oversized_channel(257, 'c'); std::string query_str = "test query"; @@ -696,7 +644,7 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapter_RejectsOversizedChannelName) { // 合法长度 <= 256 { - test::AdapterHarness harness(in_conv, in_bindings); + test::AdapterHarness harness(in_conv); CompanyString cs_chan{static_cast(valid_channel.size()), const_cast(valid_channel.data())}; CompanyOperatorAuditInput in{5001, &cs_query, &cs_chan}; @@ -705,7 +653,7 @@ TEST_F(AdapterPurityTest, DialogueAuditAdapter_RejectsOversizedChannelName) { // 超长 > 256 { - test::AdapterHarness harness(in_conv, in_bindings); + test::AdapterHarness harness(in_conv); CompanyString cs_chan{static_cast(oversized_channel.size()), const_cast(oversized_channel.data())}; CompanyOperatorAuditInput in{5002, &cs_query, &cs_chan}; @@ -728,9 +676,6 @@ TEST_F(AdapterPurityTest, "doc_answer.plain.operator.v1"); ASSERT_NE(op_conv, nullptr); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}); OutputEncodeOptions options; options.request_ids = &request_ids; options.converter_id = op_conv->converter_id; @@ -754,8 +699,7 @@ TEST_F(AdapterPurityTest, size_t written = 0; AdapterStatus status; - int ret = op_conv->encode_fn(&ctx, bindings, options, &small_dest, &written, - &status); + int ret = op_conv->encode_fn(&ctx, options, &small_dest, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_ERR_BUFFER_TOO_SMALL); } @@ -778,8 +722,7 @@ TEST_F(AdapterPurityTest, size_t written = 0; AdapterStatus status; - int ret = op_conv->encode_fn(&ctx, bindings, options, &op_dest, &written, - &status); + int ret = op_conv->encode_fn(&ctx, options, &op_dest, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(op_out.request_id, 10U); @@ -791,9 +734,6 @@ TEST_F(AdapterPurityTest, DocAnswerExactCapacityAndOneByteOverflow) { const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( "doc_answer.plain.operator.v1"); ASSERT_NE(converter, nullptr); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}); const std::vector request_ids{42}; OutputEncodeOptions options; options.request_ids = &request_ids; @@ -825,8 +765,7 @@ TEST_F(AdapterPurityTest, DocAnswerExactCapacityAndOneByteOverflow) { size_t written = 0; AdapterStatus status; EXPECT_EQ( - converter->encode_fn(&context, bindings, options, &view, &written, - &status), + converter->encode_fn(&context, options, &view, &written, &status), overflow ? COMPANY_ALG_ERR_BUFFER_TOO_SMALL : COMPANY_ALG_SUCCESS); EXPECT_EQ(written, overflow ? 0U : 1U); EXPECT_STREQ(intent, "QA"); @@ -852,8 +791,7 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); - test::AdapterHarness harness( - in_conv, InputPortBindings({{"input_sentences", "input_sentences"}})); + test::AdapterHarness harness(in_conv); std::string buffer = "{\"query\":\"original query\"}"; CompanyString cs_buf{static_cast(buffer.size()), buffer.data()}; @@ -878,10 +816,7 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { "translate.json.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"input_sentences", "input_sentences"}}), - OutputPortBindings({{"llm_answers", "llm_answers"}})); + test::AdapterHarness harness(in_conv, out_conv); std::string q0 = "{\"query\":\"q0\"}"; std::string q1 = "{\"query\":\"q1\"}"; @@ -916,8 +851,7 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_AllSamplesValidatedBeforePublish) { "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); - test::AdapterHarness harness( - in_conv, InputPortBindings({{"input_sentences", "input_sentences"}})); + test::AdapterHarness harness(in_conv); std::string valid_q = "{\"query\":\"valid\"}"; std::string invalid_q = "invalid json"; @@ -940,10 +874,7 @@ TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - out_conv, OutputPortBindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}})); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{1001, 2002}); @@ -1006,9 +937,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { // 用 entity binding 解码输入 { - test::AdapterHarness harness( - entity_conv, - InputPortBindings({{"input_sentences", "input_sentences"}})); + test::AdapterHarness harness(entity_conv); std::string text_str = "entity sentence"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -1022,9 +951,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { // 用 keyword binding 解码输入 { - test::AdapterHarness harness( - keyword_conv, - InputPortBindings({{"input_sentences", "input_sentences"}})); + test::AdapterHarness harness(keyword_conv); std::string text_str = "keyword sentence"; CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; @@ -1060,11 +987,9 @@ TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { "document.structured.operator.v1"); ASSERT_NE(out_conv, nullptr); - OutputPortBindings bindings({{"extracted_entities", "extracted_entities"}}); - // 上下文 A:实体抽取 Pipeline 的输出 { - test::AdapterHarness harness(out_conv, bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{9001}); StructuredDocumentBatch batch; batch.emplace_back( @@ -1082,7 +1007,7 @@ TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { // 上下文 B:产出相同 schema 的通用结构化 JSON Pipeline 输出 { - test::AdapterHarness harness(out_conv, bindings); + test::AdapterHarness harness(out_conv); harness.SetRequestIds(std::vector{9002}); StructuredDocumentBatch batch; batch.emplace_back( @@ -1110,11 +1035,9 @@ TEST_F(AdapterPurityTest, ExternalSlotDefinition("inputs", "CustomMultiFieldInput", PortDirection::kInput, true, "custom_input")}; custom_in_def.max_batch_size = 64; - custom_in_def.logical_ports = { - NodePortDefinition("texts", "TextBatch", true, "1:1")}; + custom_in_def.logical_ports = {OutputPort(kInputSentences)}; custom_in_def.decode_fn = [](const ExternalInputBatchView& src, const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* ctx, AdapterStatus* status) -> int { std::vector ids; TextBatch texts; @@ -1129,7 +1052,7 @@ TEST_F(AdapterPurityTest, if (!PublishRequestIds(options, std::move(ids), status)) { return status->Code(); } - ctx->Publish(bindings.GetActualKey("texts"), texts); + ctx->Publish(kInputSentences, texts); return 0; }; @@ -1151,14 +1074,13 @@ TEST_F(AdapterPurityTest, view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; view.count = 1; - InputPortBindings bindings({{"input_sentences", "input_sentences"}}); std::vector request_ids; InputDecodeOptions opts; opts.request_ids = &request_ids; opts.converter_id = in_a->converter_id; AdapterStatus st; - ASSERT_EQ(in_a->decode_fn(view, opts, bindings, &ctx_a, &st), 0); + ASSERT_EQ(in_a->decode_fn(view, opts, &ctx_a, &st), 0); } // 格式 B:经 test.multi_field.operator.v1 的 CustomMultiFieldInput @@ -1173,14 +1095,13 @@ TEST_F(AdapterPurityTest, view.slot_types["inputs"] = "CustomMultiFieldInput"; view.count = 1; - InputPortBindings bindings({{"texts", "input_sentences"}}); std::vector request_ids; InputDecodeOptions opts; opts.request_ids = &request_ids; opts.converter_id = in_b->converter_id; AdapterStatus st; - ASSERT_EQ(in_b->decode_fn(view, opts, bindings, &ctx_b, &st), 0); + ASSERT_EQ(in_b->decode_fn(view, opts, &ctx_b, &st), 0); } const auto* texts_a = ctx_a.Read("input_sentences"); @@ -1201,9 +1122,7 @@ TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { ASSERT_NE(out_a, nullptr); EntityOutputFixture fix; std::vector outputs = {fix.out}; - test::AdapterHarness harness( - out_a, - OutputPortBindings({{"extracted_entities", "extracted_entities"}})); + test::AdapterHarness harness(out_a); harness.SetRequestIds(std::vector{5001}); harness.Publish( "extracted_entities", @@ -1224,8 +1143,7 @@ TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { ASSERT_NE(out_b, nullptr); KeywordOutputFixture fix; std::vector outputs = {fix.out}; - test::AdapterHarness harness( - out_b, OutputPortBindings({{"rule_matches", "rule_matches"}})); + test::AdapterHarness harness(out_b); harness.SetRequestIds(std::vector{5001}); RuleMatchItem urgent(1, "URGENT", "急", 1.0f); urgent.slots["flag"] = "urgent"; @@ -1260,7 +1178,6 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { plain_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; plain_view.count = 1; - InputPortBindings bindings({{"input_sentences", "input_sentences"}}); std::vector request_ids; InputDecodeOptions opts; opts.request_ids = &request_ids; @@ -1270,7 +1187,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { AlgContext ctx; AdapterStatus st; opts.converter_id = plain_conv->converter_id; - EXPECT_EQ(plain_conv->decode_fn(plain_view, opts, bindings, &ctx, &st), 0); + EXPECT_EQ(plain_conv->decode_fn(plain_view, opts, &ctx, &st), 0); const auto* s = ctx.Read("input_sentences"); ASSERT_NE(s, nullptr); EXPECT_EQ((*s)[0].data, "Hello plain text"); @@ -1281,7 +1198,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { AlgContext ctx; AdapterStatus st; opts.converter_id = json_conv->converter_id; - EXPECT_EQ(json_conv->decode_fn(plain_view, opts, bindings, &ctx, &st), + EXPECT_EQ(json_conv->decode_fn(plain_view, opts, &ctx, &st), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(st.FieldPath(), "json"); } @@ -1300,7 +1217,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { AlgContext ctx; AdapterStatus st; opts.converter_id = json_conv->converter_id; - EXPECT_EQ(json_conv->decode_fn(json_view, opts, bindings, &ctx, &st), 0); + EXPECT_EQ(json_conv->decode_fn(json_view, opts, &ctx, &st), 0); const auto* s = ctx.Read("input_sentences"); ASSERT_NE(s, nullptr); EXPECT_EQ((*s)[0].data, "Hello JSON"); diff --git a/tests/unit/adapter/test_complex_converters.cpp b/tests/unit/adapter/test_complex_converters.cpp index bb58b4c6..cb8404c6 100644 --- a/tests/unit/adapter/test_complex_converters.cpp +++ b/tests/unit/adapter/test_complex_converters.cpp @@ -44,8 +44,6 @@ TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { in_view.slots["doc_in"] = llm_edgeflow::BorrowInputForTest({&doc_in}); in_view.slot_types["doc_in"] = "CompanyOperatorDocInput"; - InputPortBindings in_bindings( - {{"raw_docs", "raw_docs"}, {"raw_queries", "raw_queries"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -53,7 +51,7 @@ TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { AlgContext ctx; AdapterStatus status; - int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int ret = in_conv->decode_fn(in_view, in_options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); // 填充答案上下文 @@ -88,16 +86,12 @@ TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { out_view.SetCapacity("doc_out", "answer_text", 255); out_view.SetCapacity("doc_out", "intent_name", 63); - OutputPortBindings out_bindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status); + ret = out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(doc_out.request_id, 2001U); @@ -120,9 +114,6 @@ TEST_F(ComplexConvertersTest, ctx.Publish("llm_answers", TextBatch{{0, 0, answer}}); ctx.Publish("intent_matches", RuleMatchBatch{{0, 0, RuleMatchItem{}}}); ctx.Publish("doc_chunk_counts", Int32Batch{{0, 0, 1}}); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}); OutputEncodeOptions options; options.request_ids = &request_ids; options.converter_id = converter->converter_id; @@ -141,9 +132,8 @@ TEST_F(ComplexConvertersTest, view.SetCapacity("doc_out", "intent_name", 0); size_t written = 0; AdapterStatus status; - ASSERT_EQ( - converter->encode_fn(&ctx, bindings, options, &view, &written, &status), - COMPANY_ALG_SUCCESS); + ASSERT_EQ(converter->encode_fn(&ctx, options, &view, &written, &status), + COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); ASSERT_EQ(answer_out.length, 3); EXPECT_EQ(std::string(answer_out.data, answer_out.length), answer); @@ -151,9 +141,8 @@ TEST_F(ComplexConvertersTest, view.SetCapacity("doc_out", "answer_text", 1); written = 0; - EXPECT_NE( - converter->encode_fn(&ctx, bindings, options, &view, &written, &status), - COMPANY_ALG_SUCCESS); + EXPECT_NE(converter->encode_fn(&ctx, options, &view, &written, &status), + COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 0U); } @@ -184,9 +173,6 @@ TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { in_view.slots["rerank_in"] = llm_edgeflow::BorrowInputForTest({&rerank_in}); in_view.slot_types["rerank_in"] = "CompanyOperatorRerankInput"; - InputPortBindings in_bindings({{"rerank_queries", "rerank_queries"}, - {"rerank_candidates", "rerank_candidates"}, - {"rerank_pairs", "rerank_pairs"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -194,7 +180,7 @@ TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { AlgContext ctx; AdapterStatus status; - int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int ret = in_conv->decode_fn(in_view, in_options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); // 在上下文中准备 ranked_results @@ -213,14 +199,12 @@ TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { out_view.leased_slots["rerank_out"] = {&rerank_out}; out_view.slot_types["rerank_out"] = "CompanyOperatorRerankOutput"; - OutputPortBindings out_bindings({{"ranked_results", "ranked_results"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status); + ret = out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(rerank_out.request_id, 3001U); @@ -251,8 +235,6 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { in_view.slots["audit_in"] = llm_edgeflow::BorrowInputForTest({&audit_in}); in_view.slot_types["audit_in"] = "CompanyOperatorAuditInput"; - InputPortBindings in_bindings( - {{"user_texts", "user_texts"}, {"channel_names", "channel_names"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -260,7 +242,7 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { AlgContext ctx; AdapterStatus status; - int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int ret = in_conv->decode_fn(in_view, in_options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); // 在上下文中准备审核结果 @@ -295,16 +277,12 @@ TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { out_view.SetCapacity("audit_out", "matched_policy_clause", 255); out_view.SetCapacity("audit_out", "audit_verdict_json", 1023); - OutputPortBindings out_bindings( - {{"structured_verdicts", "structured_verdicts"}, - {"matched_policy", "matched_policy"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status); + ret = out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(audit_out.request_id, 4001U); @@ -334,7 +312,6 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentOperatorInputAndOutput) { in_view.slots["audio_in"] = llm_edgeflow::BorrowInputForTest({&audio_in}); in_view.slot_types["audio_in"] = "CompanyOperatorAudioInput"; - InputPortBindings in_bindings({{"audio_inputs", "audio_inputs"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -342,7 +319,7 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentOperatorInputAndOutput) { AlgContext ctx; AdapterStatus status; - int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int ret = in_conv->decode_fn(in_view, in_options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); TextBatch transcripts; @@ -373,15 +350,12 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentOperatorInputAndOutput) { out_view.SetCapacity("audio_out", "transcribed_text", 511); out_view.SetCapacity("audio_out", "intent_slot_json", 1023); - OutputPortBindings out_bindings( - {{"transcripts", "transcripts"}, {"intent_slots", "intent_slots"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status); + ret = out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(audio_out.request_id, 5001U); @@ -423,8 +397,6 @@ TEST_F(ComplexConvertersTest, OcrInvoiceQaOperatorInputAndOutput) { in_view.slot_types["frame"] = "CompanyFrame"; in_view.slot_types["string"] = "CompanyString"; - InputPortBindings in_bindings( - {{"image_paths", "image_paths"}, {"user_queries", "user_queries"}}); std::vector request_ids; InputDecodeOptions in_options; in_options.request_ids = &request_ids; @@ -432,7 +404,7 @@ TEST_F(ComplexConvertersTest, OcrInvoiceQaOperatorInputAndOutput) { AlgContext ctx; AdapterStatus status; - int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); + int ret = in_conv->decode_fn(in_view, in_options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); // 在 AlgContext 中准备发票输出 @@ -462,16 +434,12 @@ TEST_F(ComplexConvertersTest, OcrInvoiceQaOperatorInputAndOutput) { out_view.SetCapacity("od_out", "result_json", 255); out_view.count = 1; - OutputPortBindings out_bindings( - {{"extracted_invoice_json", "extracted_invoice_json"}, - {"ocr_docs", "ocr_docs"}}); OutputEncodeOptions out_options; out_options.request_ids = &request_ids; out_options.converter_id = out_conv->converter_id; size_t written = 0; - ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, - &written, &status); + ret = out_conv->encode_fn(&ctx, out_options, &out_view, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(od_out.request_id, 6001U); diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index 480d3abd..ad5f1c5e 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -30,14 +30,14 @@ int dummy_decode_calls = 0; int dummy_encode_calls = 0; int DummyDecode(const ExternalInputBatchView&, const InputDecodeOptions&, - const InputPortBindings&, AlgContext*, AdapterStatus*) { + AlgContext*, AdapterStatus*) { ++dummy_decode_calls; return 0; } -int DummyEncode(AlgContext*, const OutputPortBindings&, - const OutputEncodeOptions&, ExternalOutputBatchView*, - size_t* written_count, AdapterStatus*) { +int DummyEncode(AlgContext*, const OutputEncodeOptions&, + ExternalOutputBatchView*, size_t* written_count, + AdapterStatus*) { ++dummy_encode_calls; if (written_count) *written_count = 1; return 0; @@ -1488,8 +1488,6 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { EXPECT_TRUE(prepared.binding.binding_id.empty()); EXPECT_EQ(prepared.input_converter, nullptr); EXPECT_EQ(prepared.output_converter, nullptr); - EXPECT_TRUE(prepared.input_port_bindings.All().empty()); - EXPECT_TRUE(prepared.output_port_bindings.All().empty()); EXPECT_EQ(prepared.effective_max_batch_size, 0u); EXPECT_TRUE(prepared.output_specs.empty()); EXPECT_TRUE(prepared.output_parameter_texts.empty()); @@ -1519,8 +1517,6 @@ TEST_F(IoBindingRegistryTest, PrepareFailureResetsPreparedStateAtomically_T18) { EXPECT_TRUE(prepared.binding.binding_id.empty()); EXPECT_EQ(prepared.input_converter, nullptr); EXPECT_EQ(prepared.output_converter, nullptr); - EXPECT_TRUE(prepared.input_port_bindings.All().empty()); - EXPECT_TRUE(prepared.output_port_bindings.All().empty()); EXPECT_EQ(prepared.effective_max_batch_size, 0u); EXPECT_TRUE(prepared.output_specs.empty()); EXPECT_TRUE(prepared.output_parameter_texts.empty()); diff --git a/tests/unit/adapter/test_io_converters.cpp b/tests/unit/adapter/test_io_converters.cpp index 02fd206f..44f49075 100644 --- a/tests/unit/adapter/test_io_converters.cpp +++ b/tests/unit/adapter/test_io_converters.cpp @@ -16,13 +16,13 @@ namespace llm_edgeflow { namespace { int DummyDecode(const ExternalInputBatchView&, const InputDecodeOptions&, - const InputPortBindings&, AlgContext*, AdapterStatus*) { + AlgContext*, AdapterStatus*) { return 0; } -int DummyEncode(AlgContext*, const OutputPortBindings&, - const OutputEncodeOptions&, ExternalOutputBatchView*, - size_t* written_count, AdapterStatus*) { +int DummyEncode(AlgContext*, const OutputEncodeOptions&, + ExternalOutputBatchView*, size_t* written_count, + AdapterStatus*) { if (written_count) *written_count = 1; return 0; } @@ -38,7 +38,7 @@ struct ReproB { DECLARE_EXTERNAL_TYPE_TRAITS(ReproA, "ReproA"); DECLARE_EXTERNAL_TYPE_TRAITS(ReproB, "ReproB"); -TEST(IoConverterTest, ViewAccessorsAndPortBindings) { +TEST(IoConverterTest, ViewAccessors) { // 1. ExternalInputBatchView Slot 访问 ExternalInputBatchView in_view; int sample_int = 42; @@ -69,29 +69,6 @@ TEST(IoConverterTest, ViewAccessorsAndPortBindings) { EXPECT_EQ(out_view.GetSlot("out_slot", 0), nullptr); EXPECT_EQ(out_view.GetSlotCapacity("out_slot", "field_1"), 1024U); EXPECT_EQ(out_view.GetSlotCapacity("out_slot", "unknown", 42), 42U); - - // 3. PortBindings - InputPortBindings in_bindings({{"texts", "input_sentences"}}); - auto key = in_bindings.Key("texts"); - EXPECT_STREQ(key.name, "input_sentences"); - EXPECT_EQ(in_bindings.GetActualKey("texts"), "input_sentences"); - EXPECT_TRUE(in_bindings.HasKey("texts")); - EXPECT_EQ(in_bindings.GetActualKey("unknown"), ""); - EXPECT_FALSE(in_bindings.HasKey("unknown")); - - OutputPortBindings out_bindings({{"answers", "llm_answers"}}); - auto out_key = out_bindings.Key("answers"); - EXPECT_STREQ(out_key.name, "llm_answers"); - EXPECT_TRUE(out_bindings.HasKey("answers")); - EXPECT_EQ(out_bindings.GetActualKey("unknown"), ""); - EXPECT_FALSE(out_bindings.HasKey("unknown")); - EXPECT_STREQ(out_bindings.Key("unknown").name, ""); - - // 共享查找把方向编码进类型:解码器不能接收输出 binding, - // 编码器不能接收输入 binding。 - static_assert(!std::is_same_v); - static_assert(!std::is_convertible_v); - static_assert(!std::is_convertible_v); } TEST(IoConverterTest, RegisterAndFindInputConverter) { @@ -360,16 +337,15 @@ TEST(IoConverterTest, OptionalInputSlotsPreserveEveryFramePosition) { TEST(IoConverterTest, HarnessPreservesNamedSlotsTypesAndPoolCapacities) { InputConverterDefinition input; input.decode_fn = [](const ExternalInputBatchView& view, - const InputDecodeOptions&, const InputPortBindings&, - AlgContext* context, AdapterStatus*) -> int { + const InputDecodeOptions&, AlgContext* context, + AdapterStatus*) -> int { const auto* a = view.GetSlot("left", 0); const auto* b = view.GetSlot("right", 0); if (!a || !b || view.GetSlot("left", 0)) return -3; return context->Publish("sum", a->a + static_cast(b->b)) ? 0 : -3; }; OutputConverterDefinition output; - output.encode_fn = [](AlgContext* context, const OutputPortBindings&, - const OutputEncodeOptions&, + output.encode_fn = [](AlgContext* context, const OutputEncodeOptions&, ExternalOutputBatchView* view, size_t* written, AdapterStatus*) -> int { auto* a = view->GetSlot("left", 0); @@ -410,14 +386,13 @@ TEST(IoConverterTest, HarnessSingleSlotUsesDeclaredSlotType) { InputConverterDefinition input; input.external_slots = {{"value", "ReproA"}}; input.decode_fn = [](const ExternalInputBatchView& view, - const InputDecodeOptions&, const InputPortBindings&, - AlgContext*, AdapterStatus*) -> int { + const InputDecodeOptions&, AlgContext*, + AdapterStatus*) -> int { return view.GetSlot("value", 0) ? 0 : -3; }; OutputConverterDefinition output; output.external_slots = {{"value", "ReproB", PortDirection::kOutput}}; - output.encode_fn = [](AlgContext*, const OutputPortBindings&, - const OutputEncodeOptions&, + output.encode_fn = [](AlgContext*, const OutputEncodeOptions&, ExternalOutputBatchView* view, size_t* written, AdapterStatus*) -> int { if (!view->GetSlot("value", 0)) return -3; @@ -541,20 +516,18 @@ TEST(IoConverterTest, DecodeRowsOwnsPayloadsAndSeparatesDuplicateExternalIds) { options.max_batch_size = 2; std::vector request_ids; options.request_ids = &request_ids; - InputPortBindings bindings({{"texts", "actual_texts"}}); AlgContext context; AdapterStatus status; - ASSERT_EQ( - DecodeRequestRows( - source, options, bindings, &context, &status, "input", kRowTexts, - [](const CompanyOperatorKeywordInput& row, std::string* value) { - *value = CopyInputString(*row.sentence_text); - return AdapterStatus::Ok(); - }), - COMPANY_ALG_SUCCESS); + ASSERT_EQ(DecodeRequestRows( + source, options, &context, &status, "input", kRowTexts, + [](const CompanyOperatorKeywordInput& row, std::string* value) { + *value = CopyInputString(*row.sentence_text); + return AdapterStatus::Ok(); + }), + COMPANY_ALG_SUCCESS); bytes[0] = 'x'; EXPECT_EQ(request_ids, (std::vector{42, 42})); - const auto* texts = context.Read("actual_texts"); + const auto* texts = context.Read(kRowTexts); ASSERT_NE(texts, nullptr); ASSERT_EQ(texts->size(), 2U); for (size_t i = 0; i < texts->size(); ++i) { @@ -562,7 +535,6 @@ TEST(IoConverterTest, DecodeRowsOwnsPayloadsAndSeparatesDuplicateExternalIds) { EXPECT_EQ((*texts)[i].sub_id, 0U); EXPECT_EQ((*texts)[i].data, std::string("a\0b", 3)); } - EXPECT_FALSE(context.Has("texts")); } TEST(IoConverterTest, DecodeRowsReportsCallbackFailureWithoutPublishingBatch) { @@ -576,26 +548,24 @@ TEST(IoConverterTest, DecodeRowsReportsCallbackFailureWithoutPublishingBatch) { options.max_batch_size = 2; std::vector request_ids; options.request_ids = &request_ids; - InputPortBindings bindings({{"texts", "actual_texts"}}); AlgContext context; AdapterStatus status; - EXPECT_EQ( - DecodeRequestRows( - source, options, bindings, &context, &status, "input", kRowTexts, - [](const CompanyOperatorKeywordInput& row, std::string* value) { - if (row.request_id == 2) - return AdapterStatus::InvalidInput("bad sentence", - "sentence_text"); - *value = "accepted"; - return AdapterStatus::Ok(); - }), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ(DecodeRequestRows( + source, options, &context, &status, "input", kRowTexts, + [](const CompanyOperatorKeywordInput& row, std::string* value) { + if (row.request_id == 2) + return AdapterStatus::InvalidInput("bad sentence", + "sentence_text"); + *value = "accepted"; + return AdapterStatus::Ok(); + }), + COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.AdapterName(), options.converter_id); EXPECT_EQ(status.SampleIndex(), 1); EXPECT_EQ(status.FieldPath(), "sentence_text"); EXPECT_EQ(status.Message(), "bad sentence"); EXPECT_TRUE(request_ids.empty()); - EXPECT_FALSE(context.Has("actual_texts")); + EXPECT_FALSE(context.Has(kRowTexts.name)); } TEST(IoConverterTest, DecodeRowsUsesEffectiveBatchLimitFromOptions) { @@ -606,7 +576,6 @@ TEST(IoConverterTest, DecodeRowsUsesEffectiveBatchLimitFromOptions) { source.count = 2; source.slots["input"] = BorrowInputForTest({&first, &second}); source.slot_types["input"] = "CompanyOperatorKeywordInput"; - InputPortBindings bindings({{"texts", "actual_texts"}}); const auto decode = [&](size_t limit, AlgContext* context, AdapterStatus* status) { InputDecodeOptions options; @@ -615,7 +584,7 @@ TEST(IoConverterTest, DecodeRowsUsesEffectiveBatchLimitFromOptions) { std::vector request_ids; options.request_ids = &request_ids; return DecodeRequestRows( - source, options, bindings, context, status, "input", kRowTexts, + source, options, context, status, "input", kRowTexts, [](const CompanyOperatorKeywordInput& row, std::string* value) { *value = CopyInputString(*row.sentence_text); return AdapterStatus::Ok(); @@ -628,21 +597,20 @@ TEST(IoConverterTest, DecodeRowsUsesEffectiveBatchLimitFromOptions) { COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_NE(limited_status.Message().find("Batch size out of range [1, 1]"), std::string::npos); - EXPECT_FALSE(limited.Has("actual_texts")); + EXPECT_FALSE(limited.Has(kRowTexts.name)); // 0 表示调用方未传上限;只检查是否为空。 AlgContext unlimited; AdapterStatus unlimited_status; EXPECT_EQ(decode(0, &unlimited, &unlimited_status), COMPANY_ALG_SUCCESS); - EXPECT_TRUE(unlimited.Has("actual_texts")); + EXPECT_TRUE(unlimited.Has(kRowTexts.name)); } TEST(IoConverterTest, EncodeRowsRestoresOrderAndIdsAndChecksWriterCapacity) { AlgContext context; const std::vector request_ids{91, 17}; - context.Publish("actual_texts", + context.Publish(kRowTexts, TextBatch{{1, 0, "two"}, {0, 0, std::string("a\0b", 3)}}); - OutputPortBindings bindings({{"texts", "actual_texts"}}); OutputEncodeOptions options; options.converter_id = "test.rows.output"; options.request_ids = &request_ids; @@ -664,7 +632,7 @@ TEST(IoConverterTest, EncodeRowsRestoresOrderAndIdsAndChecksWriterCapacity) { AdapterStatus status; size_t written = 99; ASSERT_EQ(EncodeResultRows( - &context, bindings, options, &view, &written, &status, "output", + &context, options, &view, &written, &status, "output", kRowTexts, encode), COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 2U); @@ -680,7 +648,7 @@ TEST(IoConverterTest, EncodeRowsRestoresOrderAndIdsAndChecksWriterCapacity) { view.SetCapacity("output", "entities_json", 1); written = 99; EXPECT_EQ(EncodeResultRows( - &context, bindings, options, &view, &written, &status, "output", + &context, options, &view, &written, &status, "output", kRowTexts, encode), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(written, 0U); @@ -689,17 +657,17 @@ TEST(IoConverterTest, EncodeRowsRestoresOrderAndIdsAndChecksWriterCapacity) { EXPECT_EQ(status.FieldPath(), "entities_json"); written = 99; - EXPECT_EQ(EncodeResultRows( - &context, bindings, options, &view, &written, &status, "output", - kRowTexts, - [](const std::string&, CompanyOperatorEntityOutput* row, - const OutputStringWriter&) { - return row->request_id == 17 - ? AdapterStatus::InvalidInput("cannot encode", - "business_field") - : AdapterStatus::Ok(); - }), - COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ( + EncodeResultRows( + &context, options, &view, &written, &status, "output", kRowTexts, + [](const std::string&, CompanyOperatorEntityOutput* row, + const OutputStringWriter&) { + return row->request_id == 17 + ? AdapterStatus::InvalidInput("cannot encode", + "business_field") + : AdapterStatus::Ok(); + }), + COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(written, 0U); EXPECT_EQ(status.AdapterName(), options.converter_id); EXPECT_EQ(status.SampleIndex(), 1); diff --git a/tests/unit/adapter/test_text_converters.cpp b/tests/unit/adapter/test_text_converters.cpp index da21786d..433b63a4 100644 --- a/tests/unit/adapter/test_text_converters.cpp +++ b/tests/unit/adapter/test_text_converters.cpp @@ -38,7 +38,6 @@ TEST_F(TextConvertersTest, TextPlainOperatorInputDecodeSuccess) { view.slots["entity_in"] = llm_edgeflow::BorrowInputForTest({&s1, &s2}); view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - InputPortBindings bindings({{"input_sentences", "input_sentences"}}); std::vector request_ids; InputDecodeOptions options; options.request_ids = &request_ids; @@ -46,7 +45,7 @@ TEST_F(TextConvertersTest, TextPlainOperatorInputDecodeSuccess) { AlgContext ctx; AdapterStatus status; - int ret = conv->decode_fn(view, options, bindings, &ctx, &status); + int ret = conv->decode_fn(view, options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); ASSERT_EQ(request_ids.size(), 2U); @@ -77,7 +76,6 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { valid_view.slots["entity_in"] = llm_edgeflow::BorrowInputForTest({&valid_s}); valid_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - InputPortBindings bindings({{"input_sentences", "input_sentences"}}); std::vector request_ids; InputDecodeOptions options; options.request_ids = &request_ids; @@ -85,7 +83,7 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { AlgContext ctx; AdapterStatus status; - int ret = conv->decode_fn(valid_view, options, bindings, &ctx, &status); + int ret = conv->decode_fn(valid_view, options, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); const auto* sentences = ctx.Read("input_sentences"); @@ -107,7 +105,7 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { AlgContext bad_ctx; AdapterStatus bad_status; - ret = conv->decode_fn(invalid_view, options, bindings, &bad_ctx, &bad_status); + ret = conv->decode_fn(invalid_view, options, &bad_ctx, &bad_status); EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(bad_status.FieldPath(), "json"); } @@ -134,14 +132,13 @@ TEST_F(TextConvertersTest, TranslationJsonOutputEncodeOperator) { dest.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; dest.SetCapacity("entity_out", "entities_json", sizeof(buf) - 1); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.request_ids = &req_ids; options.converter_id = conv->converter_id; size_t written = 0; AdapterStatus status; - int ret = conv->encode_fn(&ctx, bindings, options, &dest, &written, &status); + int ret = conv->encode_fn(&ctx, options, &dest, &written, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(out_struct.request_id, 3001U); @@ -226,10 +223,7 @@ TEST_F(TextConvertersTest, SameCarrierDifferentPayloadBindingIsRejected) { options.converter_id = conv->converter_id; AlgContext context; AdapterStatus status; - EXPECT_EQ(conv->decode_fn( - view, options, - InputPortBindings({{"input_sentences", "input_sentences"}}), - &context, &status), + EXPECT_EQ(conv->decode_fn(view, options, &context, &status), COMPANY_ALG_SUCCESS); const auto* sentences = context.Read("input_sentences"); return sentences && sentences->size() == 1 ? sentences->front().data @@ -260,31 +254,26 @@ TEST_F(TextConvertersTest, MissingResultsDifferFromOutputCapacityFailures) { const auto* conv = IoConverterRegistry::Instance().FindOutputConverter( "translate.json.operator.v1"); ASSERT_NE(conv, nullptr); - OutputPortBindings bindings({{"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.converter_id = conv->converter_id; AlgContext context; TestOutputBatchView destination; AdapterStatus status; size_t written = 0; - EXPECT_EQ(conv->encode_fn(&context, bindings, options, &destination, &written, - &status), + EXPECT_EQ(conv->encode_fn(&context, options, &destination, &written, &status), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.Code(), COMPANY_ALG_ERR_INVALID_INPUT); ASSERT_TRUE(context.Publish("llm_answers", TextBatch{{0, 0, "hello"}})); - EXPECT_EQ(conv->encode_fn(&context, bindings, options, &destination, &written, - &status), + EXPECT_EQ(conv->encode_fn(&context, options, &destination, &written, &status), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(status.FieldPath(), "request_ids"); const std::vector request_ids{42}; options.request_ids = &request_ids; - EXPECT_EQ(conv->encode_fn(&context, bindings, options, &destination, &written, - &status), + EXPECT_EQ(conv->encode_fn(&context, options, &destination, &written, &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(status.FieldPath(), "destination"); destination.count = 1; - EXPECT_EQ(conv->encode_fn(&context, bindings, options, &destination, &written, - &status), + EXPECT_EQ(conv->encode_fn(&context, options, &destination, &written, &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(status.FieldPath(), "entity_out"); char bytes[2] = {}; @@ -294,8 +283,7 @@ TEST_F(TextConvertersTest, MissingResultsDifferFromOutputCapacityFailures) { destination.leased_slots["entity_out"] = {&output}; destination.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; destination.SetCapacity("entity_out", "entities_json", 1); - EXPECT_EQ(conv->encode_fn(&context, bindings, options, &destination, &written, - &status), + EXPECT_EQ(conv->encode_fn(&context, options, &destination, &written, &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(status.Code(), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(status.FieldPath(), "entities_json"); diff --git a/tests/unit/operator/test_operator_value_registry.cpp b/tests/unit/operator/test_operator_value_registry.cpp index b36b959f..31c6f141 100644 --- a/tests/unit/operator/test_operator_value_registry.cpp +++ b/tests/unit/operator/test_operator_value_registry.cpp @@ -1143,14 +1143,11 @@ TEST(OperatorValueRegistryTest, OperatorAgreesOnChannelNameBoundaries) { view.slots["audit_in"] = BorrowInputForTest({&op_input}); view.slot_types["audit_in"] = "CompanyOperatorAuditInput"; view.count = 1; - InputPortBindings port_bindings( - {{"user_texts", "user_texts"}, {"channel_names", "channel_names"}}); InputDecodeOptions options; options.converter_id = in_conv->converter_id; std::vector request_ids; options.request_ids = &request_ids; - int dec_ret = - in_conv->decode_fn(view, options, port_bindings, &ctx, nullptr); + int dec_ret = in_conv->decode_fn(view, options, &ctx, nullptr); EXPECT_EQ(dec_ret == 0, expected); EXPECT_EQ(binding->validate_external(&op_input, {}, nullptr) == 0, expected); @@ -1191,13 +1188,11 @@ TEST(OperatorValueRegistryTest, OperatorAgreesOnPcmBoundaries) { view.slots["audio_in"] = BorrowInputForTest({&op_input}); view.slot_types["audio_in"] = "CompanyOperatorAudioInput"; view.count = 1; - InputPortBindings port_bindings({{"audio_inputs", "audio_inputs"}}); InputDecodeOptions options; options.converter_id = in_conv->converter_id; std::vector request_ids; options.request_ids = &request_ids; - int dec_ret = - in_conv->decode_fn(view, options, port_bindings, &ctx, nullptr); + int dec_ret = in_conv->decode_fn(view, options, &ctx, nullptr); EXPECT_EQ(dec_ret == 0, test.valid); EXPECT_EQ(binding->validate_external(&op_input, {}, nullptr) == 0, test.valid);