diff --git a/.agents/skills/json-prompt-solution/SKILL.md b/.agents/skills/json-prompt-solution/SKILL.md index 00c613d1..7d3c9fdd 100644 --- a/.agents/skills/json-prompt-solution/SKILL.md +++ b/.agents/skills/json-prompt-solution/SKILL.md @@ -1,6 +1,6 @@ --- name: json-prompt-solution -description: Add LLM-EdgeFlow prompt solutions and demos with complete JSON requests and responses at the C ABI boundary. Reuse nodes and text carriers; keep external field selection and response assembly in registered Adapters. +description: Add LLM-EdgeFlow prompt solutions and demos with complete JSON requests and responses at the Operator SDK boundary. Reuse nodes and text carriers; keep external field selection and response assembly in registered Adapters. --- # JSON Prompt Solution @@ -10,14 +10,14 @@ description: Add LLM-EdgeFlow prompt solutions and demos with complete JSON requ ## 先确定真正的 SDK 边界 -本项目业务需求中的输入、输出指完整 C ABI 请求和响应,见根 `AGENTS.md` 与 -[业务接入边界](../../../doc/dev_guide/business_onboarding.md#输入输出以-c-abi-为边界)。 -传给 `Alg_Process` 输入结构体的字符串必须包含整个请求对象;输出应已由 Adapter +本项目业务需求中的输入、输出指完整 Operator 请求和响应,见根 `AGENTS.md` 与 +[业务接入边界](../../../doc/dev_guide/business_onboarding.md#输入输出以-operator-sdk-为边界)。 +传给 Operator `Process` 的输入槽位必须包含完整请求对象;输出由 OutputConverter 按业务契约组装完成。不能重新把边界解释为 Python、Demo 或内部节点端口。 输入是序列化对象文本,不给整个对象额外添加一层 JSON 字符串编码。 用户明确忽略的 version/endpoint/语言标识等字段不参与语义校验、路由或提示词构造。 -业务契约由创建句柄时的注册业务类型与 Pipeline 绑定选定,不能擅自按 endpoint 分发。 +业务契约由创建句柄时的注册业务绑定与 Pipeline 选定,不能擅自按 endpoint 分发。 ## 选择最小实现范围 @@ -25,14 +25,14 @@ description: Add LLM-EdgeFlow prompt solutions and demos with complete JSON requ 模型和现有契约。不要从 prose 推断资源或为新提示词添加专用 LLM 节点。 2. 外部 JSON 字段契约不变时,增加或克隆 Pipeline、必要 `.conf` 和样例即可。 改变输入/输出字段契约时,按 `llm-edgeflow-developer-guide` 的 Integration 路径, - 在注册 Adapter 内提取输入字段并序列化输出。Demo 只转换载体,不承担该业务语义。 -3. 优先复用已有 C 结构、Operator ValueType、结果打包和运行器;有合适载体时无需再建 + 在注册 Converter 内提取输入字段并序列化输出。Demo 只转换载体,不承担该业务语义。 +3. 优先复用已有 Operator 结构、ValueType、结果打包和运行器;有合适载体时无需再建 平台类型。不同业务可以复用载体,同时注册自己的契约,保持旧业务语义。 - 新生产 Adapter 需匹配 bridge,遵循业务接入指南中的注册完整性要求。 -4. 按 `CONTRIBUTING.md` 判断 RFC;追加 C ABI 业务类型需记录接口决定,普通配置不需 + 遵循业务接入指南中的注册完整性要求。 +4. 按 `CONTRIBUTING.md` 判断 RFC;追加新业务类型需记录接口决定,普通配置不需 额外审批。算法能力缺失时才考虑 custom Node,不把平台转换放进 Core 或 Nodes。 -当前翻译参照 `doc/solutions/translate.md`、`src/adapter/biz/translate_adapter.cpp` 和 +当前翻译参照 `doc/solutions/translate.md`、`src/adapter/biz/translate_bindings.cpp` 和 `configs/pipeline_translate_cpu.json`。其 `translate_v1` 复用既有文本/JSON 载体、一个 `LlmGenerateNode` 及模型实现。`sentence_text` / `entities_json` 是载体字段名称,不是业务 JSON 内的 query / translated;名称不够通用并不要求全仓改名。 @@ -44,29 +44,29 @@ JSON 内的 query / translated;名称不够通用并不要求全仓改名。 仅把约定原文交给提示词。翻译样例由 C++ JSON 解析器读取原始 `std::string`,经 `TextBatch` 直接交给生成节点;模型配置中的 system prompt 说明处理规则。 - 输出语义只有一个字符串时,优先让模型只生成结果原文,每条请求只调用一次生成。 - C++ Adapter 用 JSON 序列化器组装字段,正确处理引号、换行、反斜杠和 NUL;不要用 + C++ Converter 用 JSON 序列化器组装字段,正确处理引号、换行、反斜杠和 NUL;不要用 模型读取字段、拼协议、修复格式,也不要对模型原文擅自 trim、解析或去除包装。 该单次生成指一次文本生成调用,不是自回归解码只运行一个 token。 - 只有业务本身需要模型产生结构化语义时才引入结构化解析节点,校验字段与类型并使用 - `failure_policy=fail`。SDK 响应组装始终留在 Adapter,不靠 Demo 投影字段。 + `failure_policy=fail`。SDK 响应组装始终留在 Converter,不靠 Demo 投影字段。 不用固定示例或 fallback 冒充成功。 -- `.conf` 必须指向新 Pipeline,检查模型路径覆盖、上下文与生成长度,以及 C ABI 固定 - 输出数组和 Operator 池容量的区别。原 Profile 不会自动指向新方案。 +- `.conf` 必须指向新 Pipeline,检查模型路径覆盖、上下文与生成长度,以及 Operator + 输出池容量配置。原 Profile 不会自动指向新方案。 ## 验证与交付 - 重建注册,使用生产 `alg_pipeline_tool` 执行 Catalog、validate、plan、resolve-conf。 有意使用测试资产时全程选测试工具,不用测试工具绕过生产失败。 -- 业务 I/O 契约变化时,扩展现有 Adapter/C ABI 套件,直接调用 `Alg_Process` 输入完整对象。 +- 业务 I/O 契约变化时,扩展现有 Adapter/Operator 套件,直接调用 Operator `Process` 输入完整对象。 可用计数测试模型确定性验证原文输入、单次生成和 C++ 响应组装,再运行真实模型确认效果。 - Operator Demo 共用 Adapter,但不等于调用过导出的 C ABI 入口;报告时区分两者。 + 报告时记录实际验证链路。 - 覆盖无关字段变化、缺失/非字符串字段、转义和空串、输出类型、来源、容量与失败状态。 不为每个提示词创建新测试框架或执行文件;沿用已有套件。 - 用本次配置运行用户样例,检查原生 ID、逐条状态、汇总和最终 JSON;再执行最终门禁。 没有权重时明确真实效果未验证,按项目资产清单准备资源,不把替身输出当效果。 - 按根 `AGENTS.md` 分别委派测试编写、编译、测试执行给不同子 agent,遵循源码依赖顺序, 由主 agent 实现和复核;交付流程仍以 `CONTRIBUTING.md` 为准。 -- 交付一条可复制命令、C ABI 实际输入/输出示例、配置位置和准确的修改范围。 +- 交付一条可复制命令、Operator 实际输入/输出示例、配置位置和准确的修改范围。 若复用旧载体名,说明其含义。未经用户要求不上传或创建 PR。 本 skill 保存复用决策,不维护节点/模型 Catalog;后续相似业务继续更新这一份指引。 diff --git a/.agents/skills/llm-edgeflow-developer-guide/SKILL.md b/.agents/skills/llm-edgeflow-developer-guide/SKILL.md index 0b8fa312..711e885d 100644 --- a/.agents/skills/llm-edgeflow-developer-guide/SKILL.md +++ b/.agents/skills/llm-edgeflow-developer-guide/SKILL.md @@ -1,13 +1,13 @@ --- name: llm-edgeflow-developer-guide -description: Route LLM-EdgeFlow implementation across Integration, Orchestration, Capability Nodes and Model Execution. Use for custom Nodes, platform I/O adapters and Operator bridges, Demo data conversion, Core, Models, Backends and verification; configuration-only solution work belongs to pipeline-composer. +description: Route LLM-EdgeFlow implementation across Integration, Orchestration, Capability Nodes and Model Execution. Use for custom Nodes, platform I/O converters and bindings, Demo data conversion, Core, Models, Backends and verification; configuration-only solution work belongs to pipeline-composer. --- # LLM-EdgeFlow Developer Guide First classify the requested change. Read only the references needed for the affected layer; do not load every reference by default. -- New modality, C ABI structure/function behavior, Adapter, Operator bridge, or allowed runtime Pipeline name: read [Integration](references/integration.md). +- New modality, Operator SDK structure/function behavior, Converter, IoBinding, or allowed runtime Pipeline name: read [Integration](references/integration.md). - Demo dataset/carrier construction, result display or registration: follow [business onboarding](../../../doc/dev_guide/business_onboarding.md#统一-demo-接入). External request parsing and response assembly belong to Adapter work; load Integration for those changes even when the C carrier layout stays the same. - Pipeline lifecycle, Validator, DAG planning, `AlgContext`, `BlackboardKey`, or session behavior: read [Orchestration](references/orchestration.md). - New or modified capability Node, its parameters, or a Control handler: read [Capability Nodes](references/capability-nodes.md). Start Control work from the [compiled example](../../../doc/dev_guide/first_control.md); reuse transport and instance routing. diff --git a/.agents/skills/llm-edgeflow-developer-guide/references/integration.md b/.agents/skills/llm-edgeflow-developer-guide/references/integration.md index 88159fb1..3b05b4fe 100644 --- a/.agents/skills/llm-edgeflow-developer-guide/references/integration.md +++ b/.agents/skills/llm-edgeflow-developer-guide/references/integration.md @@ -1,43 +1,31 @@ # Integration -Use this reference for new modalities, public C structures, Adapter behavior, business registration, or allowed Pipeline names. +Use this reference for new modalities, Operator structures, Converter behavior, business binding registration, or allowed Pipeline names. -Business input/output means the complete public C ABI request/response, including serialized -payload semantics. `Unpack` and Adapter packing must implement that contract inside the SDK; -Demo/Python cannot perform the missing field selection or response assembly. Reusing a C struct -does not imply payload compatibility. Follow [the boundary and carrier distinction](../../../../doc/dev_guide/business_onboarding.md#输入输出以-c-abi-为边界). +Business input/output means the complete public Operator SDK request/response, including serialized +payload semantics. `InputConverter::Decode` and `OutputConverter::Encode` must implement that contract inside the SDK; +Demo/Python cannot perform the missing field selection or response assembly. Reusing a host struct +does not imply payload compatibility. Follow [the boundary and carrier distinction](../../../../doc/dev_guide/business_onboarding.md#输入输出以-operator-sdk-为边界). Start with [business onboarding](../../../../doc/dev_guide/business_onboarding.md) to select the requested -integration path. Reuse the Adapter when the external contract is unchanged; an existing C ABI -path edit does not automatically require a new Operator or Demo path. Adding a production -Adapter to the current shared SDK does require a matching bridge: Operator `GlobalInit` audits -all registered Adapters. For new Operator host types, also register ValueType capacity, -initialization and release. An ABI-only registration mode would require separate design. +integration path. Reuse the converters when the external contract is unchanged. Adding a production +binding to the current shared SDK requires matching input and output converters registered with `transport = "operator"`. +For new Operator host types, also register ValueType capacity, initialization and release. Register ValueTypes and named single-object output allocators through `adapter/operator_value_type.h`. Keep queue depth out of their callbacks. For multiple outputs or config-selected nested payloads, follow the [output allocation guide](../../../../doc/dev_guide/operator_output_allocation.md): each logical slot selects its outer type, allocator and normalized parameters; map keys do not infer layout. -Keep configuration reading in Create-time Integration. `OutputConfigReader` selects fixed -enum fields and returns text; use `MakeOutputParameterParser` with ordinary parameter -structs, without JSON in extension signatures or a `ToJson()` requirement. It is not a -Pipeline Node and does not run per request. -Use the existing `ResultPackingAdapter` and `MakeSingleSlotBizBridge` helpers where applicable; -the onboarding guide owns those implementation examples and optional Demo conversion steps. +Keep configuration reading in Create-time Integration. `OperatorConfigResolver` validates +slot configurations, allocator and capacities. It is not a Pipeline Node and does not run per request. -1. Public C ABI, Operator contract, or new modality changes meet the RFC threshold in +1. Public Operator contract or new modality changes meet the RFC threshold in `CONTRIBUTING.md`. Map the external contract, ownership, cardinality, batch bounds, and failure behavior before implementation. -2. Keep `include/edgeflow/c_api.h` valid C11. Its current platform data declarations come from `include/platform_mock/`; see that directory's README for the distinction from real company headers. Keep existing local mock DTOs and platform enums there, and keep framework entrypoints under `edgeflow/`. Expose only C primitives, fixed-layout C structs, pointers with documented ownership, and C enums through C headers—never STL or third-party types. -3. Preserve all six exported functions and their exception barrier in `src/adapter/c_api_adapter.cpp`: `noexcept`, `try`, `catch (const std::exception&)`, and `catch (...)`. -4. Implement biz conversion through `IBizAdapter` under `src/adapter/biz/`, using existing adapters as current patterns. Register through `REGISTER_BIZ_ADAPTER`; do not add a central dispatch switch. -5. Declare Adapter ingress/egress Blackboard ports and allowed runtime Pipeline names through its `BizDefinition` entries. The `biz_name` in Pipeline JSON must be accepted by the Adapter; a display name or Demo alias is not a substitute. -6. Copy input data when the ABI lifetime requires it, store request-scoped values in `AlgContext`, and pack output only through the documented ownership contract. +2. Operator public API lives in `include/edgeflow/operator/interface.h` and `types.h`. Platform mock types live in `include/platform_mock/operator_types.h` and `operator_data_types.h`; see that directory's README for the distinction from real company headers. +3. Preserve exported Operator functions and their exception barrier in `src/adapter/operator/operator_adapter.cpp`: `noexcept`, `try`, `catch (const std::exception&)`, and `catch (...)`. +4. Implement input conversion through `InputConverter` under `src/adapter/input/`, output conversion through `OutputConverter` under `src/adapter/output/`, and business binding through `IoBindingDefinition` under `src/adapter/biz/`. Register through `REGISTER_INPUT_CONVERTER`, `REGISTER_OUTPUT_CONVERTER`, and `REGISTER_IO_BINDING`. +5. Declare binding ingress/egress Blackboard ports and allowed runtime Pipeline names through its `BizExposureDefinition` and `IoBindingDefinition`. The `biz_name` in Pipeline JSON must match the binding. +6. Copy input data when the lifetime requires it, store request-scoped values in `AlgContext`, and pack output into leased pool slots only through the documented ownership contract. -Use `tests/contract/abi/test_adapter_contract_security.cpp`, `tests/contract/abi/test_c_abi_safety.cpp`, `tests/contract/abi/test_c11_abi_compliance.c`, and existing modality adapters as live templates. If the change also adds nodes, read `capability-nodes.md`; if it changes Core contract behavior, read `orchestration.md`. - -RFC-0044 lifecycle: `Unpack` validates biz fields before converting to owned DTOs. The unused -internal `IBizAdapter::ValidateInput` hook was removed; migrate custom overrides into `Unpack` -and rebuild extensions. Reuse `adapter/biz_input_constraints.h` for channel/audio semantic limits. -Egress metadata describes the Adapter's internal consumption, including ranked aggregation; -retain runtime provenance and output-capacity checks after static validation. +Use `tests/contract/abi/test_cpp_operator_sdk.cpp`, `tests/contract/abi/test_operator_safety.cpp`, `tests/contract/abi/test_adapter_contract_security.cpp`, and existing modality converters as live templates. If the change also adds nodes, read `capability-nodes.md`; if it changes Core contract behavior, read `orchestration.md`. diff --git a/.agents/skills/llm-edgeflow-developer-guide/references/verification.md b/.agents/skills/llm-edgeflow-developer-guide/references/verification.md index eb41c62a..3b783bbd 100644 --- a/.agents/skills/llm-edgeflow-developer-guide/references/verification.md +++ b/.agents/skills/llm-edgeflow-developer-guide/references/verification.md @@ -17,11 +17,9 @@ framework-specific evidence. this is not a workaround for a failing production configuration. See [tool selection](../../../../tools/pipeline_studio/README.md#校验工具选择). 4. When a business I/O contract changes, verify the complete original request and response by - directly calling `Alg_Process` in the existing C ABI/Adapter suite. Operator Demo success - alone does not prove that boundary; reuse existing coverage when the contract is unchanged. + directly calling Operator `Process` in the existing Operator/Adapter suite. When a Demo-supported business path changes, run that edited Pipeline through the compatible - Demo and check request IDs, status and expected output fields. For a C ABI-only path, use the - corresponding end-to-end contract tests instead of adding a new Demo solely for verification. Follow + Demo and check request IDs, status and expected output fields. Follow [running the current solution](../../../../tools/pipeline_studio/README.md#运行当前方案) for `.conf` / Profile selection and Demo Control behavior; running an unchanged Profile does not verify a new JSON file. diff --git a/.agents/skills/pipeline-composer/SKILL.md b/.agents/skills/pipeline-composer/SKILL.md index a6bfcc0c..52013869 100644 --- a/.agents/skills/pipeline-composer/SKILL.md +++ b/.agents/skills/pipeline-composer/SKILL.md @@ -12,11 +12,11 @@ and native Resolver; do not reproduce their validation rules. ## Workflow -First compare the requested complete C ABI input/output contract with the existing Adapter. -The same C carrier layout is not enough: payload fields, types and serialization must also match. +First compare the requested complete Operator SDK input/output contract with the existing Adapter / Converters. +The same carrier layout is not enough: payload fields, types and serialization must also match. Catalog ingress/egress are internal ports. A missing external conversion belongs in Integration; do not compensate by extracting request fields or assembling business responses in Demo/Python. -See [the I/O boundary](../../../doc/dev_guide/business_onboarding.md#输入输出以-c-abi-为边界). +See [the I/O boundary](../../../doc/dev_guide/business_onboarding.md#输入输出以-operator-sdk-为边界). 1. Build the tool if unavailable, and rebuild after registration changes. Query the target biz contract and its filtered assets: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ab1994c..42c83ecd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,7 @@ jobs: ./scripts/run_sanitizers.sh --ci-runtime real-c-abi-and-profile: - name: Real GGUF C ABI & Profile + name: Real GGUF Operator & Profile runs-on: ubuntu-latest env: @@ -198,7 +198,7 @@ jobs: - name: Fetch and Verify Pinned GGUF run: ./scripts/fetch_real_test_models.sh --gguf-only - - name: Run Real C ABI and Public Profile + - name: Run Real Model and Public Profile run: | ccache -z LLM_EDGEFLOW_LINKER=mold \ diff --git a/AGENTS.md b/AGENTS.md index bbfc9b31..cf48f1c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ Chinese/English names are 接入适配层 / Integration, 流程编排层 / Orche Dependencies flow downward only: ```text -Integration C ABI / Operator / Biz adapters +Integration Operator / Biz adapters ↓ Orchestration Pipeline / Validator / Catalog / Blackboard / Session ↓ @@ -26,20 +26,22 @@ Capability Nodes Request-stateless Nodes Model Execution Model semantics / neutral execution protocols / Backends ``` -- **Integration** — `include/edgeflow/c_api.h`, `include/edgeflow/operator/`, `include/platform_mock/`, - `include/adapter/`, and `src/adapter/`. Public C headers remain C11-only. All six exported - `Alg_*` functions keep `noexcept`, `catch (const std::exception&)`, and `catch (...)` - barriers. Biz-specific conversion belongs in registered `IBizAdapter` and Operator bridge - implementations, not in central dispatch switches or lower layers. - Business input/output requirements describe the complete request/response at the public - C ABI boundary. `IBizAdapter::Unpack` owns external payload validation and field selection; - Adapter packing owns response assembly and serialization. The C ABI must satisfy that contract - without Demo/Python preprocessing or postprocessing. Demo may construct carriers, hold buffers, - invoke the SDK and display/copy its results; it must not replace Adapter conversion. - Reusing the same C struct does not imply the same payload schema or business contract. - Node ports and Catalog ingress/egress describe internal values, not the external C ABI payload. - Existing local substitutes for platform public types live only in `include/platform_mock/`; - these are not company SDK headers. Keep framework entrypoints and helpers under `edgeflow/`. +- **Integration** — `include/edgeflow/operator/`, `include/platform_mock/`, `include/adapter/`, + and `src/adapter/`. The C++ Operator API (`llm_edgeflow::operator_api`) is the sole public + algorithm interface. All exported Operator table functions keep `noexcept`, + `catch (const std::exception&)`, and `catch (...)` barriers. Biz-specific conversion belongs + in registered `InputConverter`, `OutputConverter`, and `IoBinding` implementations, not in + central dispatch switches or lower layers. + Business input/output requirements describe the complete request/response at the Operator + boundary. Input converters own external payload validation and field selection; output + converters own response assembly, capacity checking, and serialization. The Operator API + must satisfy that contract without Demo/Python preprocessing or postprocessing. Demo may + construct carriers, hold buffers, invoke the SDK and display/copy its results; it must not + replace Adapter conversion. Reusing the same DTO struct does not imply the same payload + schema or business contract. Node ports and Catalog ingress/egress describe internal values, + not the external Operator payload. Existing local substitutes for platform public types live + only in `include/platform_mock/`; these are not company SDK headers. Keep framework + entrypoints and helpers under `edgeflow/`. - **Orchestration** — `include/core/` and `src/core/`. `PipelineValidator` is the single validation and planning implementation. Runtime Pipeline documents use explicit `id` and `depends_on`; `Pipeline` consumes `ValidatedPipelinePlan` without reparsing or resorting. Request values @@ -71,10 +73,10 @@ from prose. Query `alg_pipeline_tool`; registrations and Definitions are the exe necessary `.conf` files and optional Demo Profiles: read and follow [pipeline-composer](.agents/skills/pipeline-composer/SKILL.md). Reuse registered Nodes; route capability gaps to implementation before writing C++. -- C ABI/Adapter, Core/Pipeline, Node, Model, or Backend implementation: read and follow +- Operator SDK/Adapter, Core/Pipeline, Node, Model, or Backend implementation: read and follow [llm-edgeflow-developer-guide](.agents/skills/llm-edgeflow-developer-guide/SKILL.md), loading only affected-layer references. New platform structures and Demo data conversion belong here; - preserve the current SDK's Adapter/Operator bridge registry completeness when adding a biz. + preserve the current SDK's input/output converter and binding registry completeness when adding a biz. - Upload, PR, or merge requested by the user: read and follow [github-branch-merge](.agents/skills/github-branch-merge/SKILL.md). Never upload or merge from an ordinary implementation request. @@ -107,7 +109,7 @@ implementation, coordination, durable documentation, and the final report. build/tests needed for diagnosis and then the single canonical pre-delivery gate from `CONTRIBUTING.md`. Report exact commands and results. Do not silently fix source or tests; return production defects to the implementation owner and test defects to the test author. -- **Reviewer** — read-only independent review for high-risk changes: public C ABI, cross-layer +- **Reviewer** — read-only independent review for high-risk changes: public Operator SDK API, cross-layer architecture, Core/Pipeline semantics, ownership/lifetime/concurrency, Model/Backend behavior, RFC implementation, or similarly difficult-to-reverse changes. Routine low-risk edits do not require a separate reviewer. Review for correctness, architecture invariants, regression risk, diff --git a/CMakeLists.txt b/CMakeLists.txt index 22b3d068..f7088d10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.16) -project(LLMEdgeFlow VERSION 10.0.0 LANGUAGES C CXX) +project(LLMEdgeFlow VERSION 11.0.0 LANGUAGES C CXX) -set(LLM_EDGEFLOW_ABI_VERSION "6.0.0") -set(LLM_EDGEFLOW_ABI_VERSION_MAJOR 6) +set(LLM_EDGEFLOW_ABI_VERSION "7.0.0") +set(LLM_EDGEFLOW_ABI_VERSION_MAJOR 7) # 严格禁止 In-Source 构建 (防止误用 cmake . 污染源码目录并触发重复下载阻塞) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") diff --git a/README.md b/README.md index 15bfac70..dfe5e0c2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![C++17](https://img.shields.io/badge/C%2B%2B-17-blue)](CMakeLists.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE) -LLM-EdgeFlow 将规则处理、向量检索、文本生成、图像转写和语音识别组织为可复用的算法节点,用 JSON 描述它们的连接关系,再通过统一的 C ABI 或 C++ Operator 接口供宿主程序调用。 +LLM-EdgeFlow 将规则处理、向量检索、文本生成、图像转写和语音识别组织为可复用的算法节点,用 JSON 描述它们的连接关系,再通过统一的 C++ Operator SDK 供宿主程序调用。 框架面向算法方案开发者:已有能力通过配置组合,领域算法在自定义 Node 中实现,平台输入输出由 Adapter 转换。模型语义与推理后端分别扩展,便于在不同方案中复用同一套算法代码。 @@ -25,7 +25,7 @@ LLM-EdgeFlow 将规则处理、向量检索、文本生成、图像转写和语 - **组合算法流程**:Pipeline 以有向无环图(DAG)描述节点依赖,可组合规则、检索、模型调用和结果处理。执行前统一检查端口类型、依赖关系和并发写冲突。 - **复用算法实现**:通用 Node 与自定义 Node 使用相同的类型端口和注册机制;单个节点可用于多个 Pipeline。 - **管理模型执行**:Model 负责模型预后处理与输出语义,Backend 负责推理运行时。节点通过模型能力接口调用推理。 -- **对接宿主程序**:C ABI 与 Operator 共用内部算法运行时,集中处理数据转换、资源生命周期和异常隔离。 +- **对接宿主程序**:C++ Operator SDK 统一对接宿主程序,集中处理数据转换、资源生命周期和异常隔离。 - **验证运行结果**:命令行工具与 Web 工作台共用 Catalog 和 Validator;统一 Demo 输出逐条结果与运行摘要,并保留请求来源编号。 ## 快速开始 @@ -171,7 +171,7 @@ Smoke 验证执行链路;真实模型的业务效果需使用目标数据集 该命令统一执行格式与静态检查、配置构建及 CTest 测试。环境需具备 clang-format 18,以及架构图检查所需的 Java 17+;详细流程见 [CONTRIBUTING.md](CONTRIBUTING.md),测试组织见 [tests/README.md](tests/README.md),Agent 开发约束见 [AGENTS.md](AGENTS.md)。 -当前产品版本为 **v10.0.0**,公共 **ABI major 为 6**。接口边界见[架构设计](doc/architecture.md),版本记录见 [Changelog](doc/CHANGELOG.md)。 +当前产品版本为 **v11.0.0**,公共 **ABI major 为 7**。接口边界见[架构设计](doc/architecture.md),版本记录见 [Changelog](doc/CHANGELOG.md)。 ## 许可证 diff --git a/cmake_ext/IndividualTests.cmake b/cmake_ext/IndividualTests.cmake index 727901d9..2db8ed12 100644 --- a/cmake_ext/IndividualTests.cmake +++ b/cmake_ext/IndividualTests.cmake @@ -1,8 +1,8 @@ -# 3. 纯 C11 ABI 兼容性测试 (确保头文件为纯 C 且可被标准 C 编译器直接编译) -add_executable(test_c11_abi_compliance ${EDGEFLOW_SOURCE_test_c11_abi_compliance}) -target_link_libraries(test_c11_abi_compliance PRIVATE llm_edgeflow::sdk) -add_test(NAME C11AbiComplianceTest COMMAND test_c11_abi_compliance) +# 3. 纯 C++ Operator SDK 独立消费者测试 +add_executable(test_cpp_operator_sdk ${EDGEFLOW_SOURCE_test_cpp_operator_sdk}) +set_target_properties(test_cpp_operator_sdk PROPERTIES LINK_LIBRARIES "llm_edgeflow::sdk") +add_test(NAME CppOperatorSdkTest COMMAND test_cpp_operator_sdk) # 4. 架构分层防腐隔离测试 (LayerGuard) add_test(NAME LayerGuardTest COMMAND ${CMAKE_COMMAND} -E env @@ -37,9 +37,9 @@ target_link_libraries(test_log PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) add_test(NAME CompanyAlgLogTest COMMAND test_log) -add_executable(test_c_abi_safety ${EDGEFLOW_SOURCE_test_c_abi_safety}) -target_link_libraries(test_c_abi_safety PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) -add_test(NAME CAbiSafetyTest COMMAND test_c_abi_safety) +add_executable(test_operator_safety ${EDGEFLOW_SOURCE_test_operator_safety}) +target_link_libraries(test_operator_safety PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) +add_test(NAME OperatorSafetyTest COMMAND test_operator_safety) add_executable(test_qwen_causal_lm_model ${EDGEFLOW_SOURCE_test_qwen_causal_lm_model}) target_link_libraries(test_qwen_causal_lm_model PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) @@ -278,6 +278,10 @@ add_executable(test_operator_golden ${EDGEFLOW_SOURCE_test_operator_golden}) target_link_libraries(test_operator_golden PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) add_test(NAME OperatorGoldenTest COMMAND test_operator_golden) +add_executable(test_adapter_purity ${EDGEFLOW_SOURCE_test_adapter_purity}) +target_link_libraries(test_adapter_purity PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) +add_test(NAME AdapterPurityTest COMMAND test_adapter_purity) + add_executable(test_io_converters ${EDGEFLOW_SOURCE_test_io_converters}) target_link_libraries(test_io_converters PRIVATE llm_edgeflow::internal_runtime GTest::gtest GTest::gtest_main) add_test(NAME IoConverterTest COMMAND test_io_converters) @@ -300,7 +304,7 @@ add_test(NAME ComplexConvertersTest COMMAND test_complex_converters) set(EDGEFLOW_INDIVIDUAL_TESTS_WITH_RUNTIME_FIXTURES test_framework_core test_log - test_c_abi_safety + test_operator_safety test_qwen_causal_lm_model test_llama_cpp_backend test_whisper_cpp_backend @@ -316,7 +320,7 @@ set(EDGEFLOW_INDIVIDUAL_TESTS_WITH_RUNTIME_FIXTURES test_operator_api test_operator_output_pool test_operator_value_registry - test_operator_biz_bridge_registry + test_adapter_purity test_doc_qa_rerank test_pipeline_studio test_demo_runner @@ -356,11 +360,11 @@ endforeach() # 设置所有测试工作目录为项目根目录,保证无论从何处运行 CTest,相对路径均一致解析 set_tests_properties( - C11AbiComplianceTest LayerGuardTest ArchitectureDocsDriftTest + CppOperatorSdkTest LayerGuardTest ArchitectureDocsDriftTest ArchitectureDocsDriftGateSelfTest DiagramAssetsCheckTest DiagramRenderGateSelfTest ScriptGeneratorDetectionTest BatchExecutorTest FrameworkCoreTest - CAbiSafetyTest CompanyAlgLogTest QwenCausalLmModelTest LlamaCppBackendTest DifferentIoModalitiesTest + OperatorSafetyTest CompanyAlgLogTest QwenCausalLmModelTest LlamaCppBackendTest DifferentIoModalitiesTest AllBizPipelinesTest ConcurrencyAndEdgeCasesTest DagPipelineTest RuntimeControlAndHotSwapTest EngineFaultToleranceAndLifecycleTest AdapterContractSecurityTest PipelineConfigTest RegistryConflictNodeTest @@ -377,6 +381,7 @@ set_tests_properties( LlmGenerateNodeTest AsrTranscribeNodeTest OcrDetectNodeTest TextRuleMatchNodeTest StructuredJsonParseNodeTest TextCorpusSourceNodeTest CommonNodesTest FunctionNodeTest ParameterBindingTest OperatorGoldenTest + AdapterPurityTest IoConverterTest IoBindingRegistryTest TextConvertersTest ComplexConvertersTest PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" ) diff --git a/cmake_ext/LayerHeaderViews.cmake b/cmake_ext/LayerHeaderViews.cmake index d3c968d7..f3b5084c 100644 --- a/cmake_ext/LayerHeaderViews.cmake +++ b/cmake_ext/LayerHeaderViews.cmake @@ -35,11 +35,10 @@ function(edgeflow_collect_headers output_var) endfunction() set(platform_mock_headers - include/platform_mock/alg_types.h include/platform_mock/error_codes.h + include/platform_mock/error_codes.h include/platform_mock/operator_data_types.h include/platform_mock/operator_types.h) set(public_headers - include/edgeflow/c_api.h include/edgeflow/c_api.hpp include/edgeflow/export.h include/edgeflow/log.h include/edgeflow/operator/interface.h include/edgeflow/operator/types.h ${platform_mock_headers}) @@ -73,8 +72,7 @@ edgeflow_header_view(orchestration ${core_headers} ${model_api_headers}) edgeflow_collect_headers(integration_headers "${PROJECT_SOURCE_DIR}/include/adapter" "${PROJECT_SOURCE_DIR}/include/edgeflow/operator" "${PROJECT_SOURCE_DIR}/src/adapter") edgeflow_header_view(integration ${integration_headers} ${core_headers} - ${model_api_headers} ${platform_mock_headers} - include/edgeflow/c_api.h include/edgeflow/c_api.hpp) + ${model_api_headers} ${platform_mock_headers}) # Capture the actual evaluated target include paths, including transitive usage # requirements, so the existing LayerGuard gate detects accidental broadening. diff --git a/cmake_ext/TestInventory.cmake b/cmake_ext/TestInventory.cmake index 4e65931c..39b91c83 100644 --- a/cmake_ext/TestInventory.cmake +++ b/cmake_ext/TestInventory.cmake @@ -34,8 +34,8 @@ set(EDGEFLOW_REQUIRED_CONTRACT_TESTS CommonNodesTest FunctionNodeTest ParameterBindingTest - C11AbiComplianceTest - CAbiSafetyTest + CppOperatorSdkTest + OperatorSafetyTest DifferentIoModalitiesTest AllBizPipelinesTest ConcurrencyAndEdgeCasesTest @@ -64,8 +64,8 @@ set(EDGEFLOW_REQUIRED_CONTRACT_TESTS # translation unit declared once here; mode-specific files only decide how to # group processes, filters and labels. set(EDGEFLOW_SOURCE_test_adapter_contract_security "${PROJECT_SOURCE_DIR}/tests/contract/abi/test_adapter_contract_security.cpp") -set(EDGEFLOW_SOURCE_test_c11_abi_compliance "${PROJECT_SOURCE_DIR}/tests/contract/abi/test_c11_abi_compliance.c") -set(EDGEFLOW_SOURCE_test_c_abi_safety "${PROJECT_SOURCE_DIR}/tests/contract/abi/test_c_abi_safety.cpp") +set(EDGEFLOW_SOURCE_test_cpp_operator_sdk "${PROJECT_SOURCE_DIR}/tests/contract/abi/test_cpp_operator_sdk.cpp") +set(EDGEFLOW_SOURCE_test_operator_safety "${PROJECT_SOURCE_DIR}/tests/contract/abi/test_operator_safety.cpp") set(EDGEFLOW_SOURCE_test_catalog_contract_ssot "${PROJECT_SOURCE_DIR}/tests/contract/catalog/test_catalog_contract_ssot.cpp") set(EDGEFLOW_SOURCE_test_model_backend_registry_conflict "${PROJECT_SOURCE_DIR}/tests/contract/catalog/test_model_backend_registry_conflict.cpp") set(EDGEFLOW_SOURCE_test_registry_conflict "${PROJECT_SOURCE_DIR}/tests/contract/catalog/test_registry_conflict.cpp") diff --git a/cmake_ext/Tests.cmake b/cmake_ext/Tests.cmake index 7817398f..07e75125 100644 --- a/cmake_ext/Tests.cmake +++ b/cmake_ext/Tests.cmake @@ -147,7 +147,7 @@ target_link_libraries(edgeflow_test_nodes_runner PRIVATE edgeflow_enable_test_pch(edgeflow_test_nodes_runner) set(EDGEFLOW_TEST_ADAPTER_SRCS - ${EDGEFLOW_SOURCE_test_c_abi_safety} + ${EDGEFLOW_SOURCE_test_operator_safety} ${EDGEFLOW_SOURCE_test_different_io_modalities} ${EDGEFLOW_SOURCE_test_all_biz_pipelines} ${EDGEFLOW_SOURCE_test_concurrency_and_edge_cases} @@ -203,8 +203,8 @@ endif() # Process-isolated targets. Registry conflict intentionally runs each dirty # singleton scenario in its own process. -add_executable(test_c11_abi_compliance ${EDGEFLOW_SOURCE_test_c11_abi_compliance}) -target_link_libraries(test_c11_abi_compliance PRIVATE llm_edgeflow::sdk) +add_executable(test_cpp_operator_sdk ${EDGEFLOW_SOURCE_test_cpp_operator_sdk}) +set_target_properties(test_cpp_operator_sdk PROPERTIES LINK_LIBRARIES "llm_edgeflow::sdk") add_executable(test_registry_conflict ${EDGEFLOW_SOURCE_test_registry_conflict}) target_link_libraries(test_registry_conflict PRIVATE @@ -292,11 +292,11 @@ edgeflow_add_runner_test(FunctionNodeTest edgeflow_test_nodes_runner edgeflow_add_runner_test(ParameterBindingTest edgeflow_test_nodes_runner "ParameterBindingTest.*" "${_edgeflow_tier1}") -add_test(NAME C11AbiComplianceTest COMMAND test_c11_abi_compliance) -set_tests_properties(C11AbiComplianceTest PROPERTIES +add_test(NAME CppOperatorSdkTest COMMAND test_cpp_operator_sdk) +set_tests_properties(CppOperatorSdkTest PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS "${_edgeflow_tier2}") -edgeflow_add_runner_test(CAbiSafetyTest edgeflow_test_adapter_runner - "CAbiSafetyTest.*" "${_edgeflow_tier2}") +edgeflow_add_runner_test(OperatorSafetyTest edgeflow_test_adapter_runner + "OperatorSafetyTest.*" "${_edgeflow_tier2}") edgeflow_add_runner_test(DifferentIoModalitiesTest edgeflow_test_adapter_runner "DifferentIoModalitiesTest.*" "${_edgeflow_tier1}") edgeflow_add_runner_test(AllBizPipelinesTest edgeflow_test_adapter_runner @@ -499,7 +499,7 @@ set_tests_properties(PipelineToolCatalogTest PipelineToolValidateTest add_custom_target(edgeflow_dev_tests DEPENDS alg_demo alg_pipeline_tool alg_pipeline_tool_test alg_show - test_c11_abi_compliance + test_cpp_operator_sdk test_registry_conflict test_model_backend_registry_conflict test_catalog_contract_ssot edgeflow_test_core_runner edgeflow_test_nodes_runner edgeflow_test_adapter_runner diff --git a/cmake_ext/edgeflow_sdk.map.in b/cmake_ext/edgeflow_sdk.map.in index 7614dea5..3651a438 100644 --- a/cmake_ext/edgeflow_sdk.map.in +++ b/cmake_ext/edgeflow_sdk.map.in @@ -1,11 +1,5 @@ LLM_EDGEFLOW_@LLM_EDGEFLOW_ABI_VERSION_MAJOR@ { global: - Alg_Init; - Alg_Create; - Alg_Process; - Alg_Control; - Alg_Destroy; - Alg_DeInit; AlgBase_setLogLevelByName; AlgBase_getLogLevelByName; AlgBase_logPrint; diff --git a/configs/pipeline_audio_asr_cabi.json b/configs/pipeline_audio_asr_cabi.json deleted file mode 100644 index 55451243..00000000 --- a/configs/pipeline_audio_asr_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_audio_asr_cpu.json", - "io_binding": "audio_asr_intent.cabi.v1" - } -} diff --git a/configs/pipeline_cross_rerank_cabi.json b/configs/pipeline_cross_rerank_cabi.json deleted file mode 100644 index 9ea57aae..00000000 --- a/configs/pipeline_cross_rerank_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_cross_rerank_cpu.json", - "io_binding": "cross_rerank.cabi.v1" - } -} diff --git a/configs/pipeline_dialogue_audit_cabi.json b/configs/pipeline_dialogue_audit_cabi.json deleted file mode 100644 index 56b4c688..00000000 --- a/configs/pipeline_dialogue_audit_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_dialogue_audit_default.json", - "io_binding": "compliance_audit.cabi.v1" - } -} diff --git a/configs/pipeline_doc_qa_cabi.json b/configs/pipeline_doc_qa_cabi.json deleted file mode 100644 index fee2ebe6..00000000 --- a/configs/pipeline_doc_qa_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_doc_qa_cpu.json", - "io_binding": "doc_qa.cabi.v1" - } -} diff --git a/configs/pipeline_doc_qa_rerank_cabi.json b/configs/pipeline_doc_qa_rerank_cabi.json deleted file mode 100644 index b7abd729..00000000 --- a/configs/pipeline_doc_qa_rerank_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_doc_qa_rerank_cpu.json", - "io_binding": "doc_qa.cabi.v1" - } -} diff --git a/configs/pipeline_entity_extract_cabi.json b/configs/pipeline_entity_extract_cabi.json deleted file mode 100644 index f87ba5d9..00000000 --- a/configs/pipeline_entity_extract_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_entity_extract_cpu.json", - "io_binding": "entity_extract.cabi.v1" - } -} diff --git a/configs/pipeline_keyword_match_cabi.json b/configs/pipeline_keyword_match_cabi.json deleted file mode 100644 index 8929db60..00000000 --- a/configs/pipeline_keyword_match_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_keyword_match_rules.json", - "io_binding": "keyword_match.cabi.v1" - } -} diff --git a/configs/pipeline_ocr_doc_qa_cabi.json b/configs/pipeline_ocr_doc_qa_cabi.json deleted file mode 100644 index fa47ac17..00000000 --- a/configs/pipeline_ocr_doc_qa_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_ocr_doc_qa_kite.json", - "io_binding": "ocr_doc_qa.cabi.v1" - } -} diff --git a/configs/pipeline_translate_cabi.json b/configs/pipeline_translate_cabi.json deleted file mode 100644 index 587266a2..00000000 --- a/configs/pipeline_translate_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_translate_cpu.json", - "io_binding": "translate.cabi.v1" - } -} diff --git a/demo/common/demo_registry.h b/demo/common/demo_registry.h index b6829859..79f73ea5 100644 --- a/demo/common/demo_registry.h +++ b/demo/common/demo_registry.h @@ -8,7 +8,6 @@ #include #include "demo/common/demo_options.h" -#include "edgeflow/c_api.h" namespace alg_demo { diff --git a/demo/common/operator_runner.h b/demo/common/operator_runner.h index 95a69f44..7afa20e8 100644 --- a/demo/common/operator_runner.h +++ b/demo/common/operator_runner.h @@ -13,7 +13,6 @@ #include "demo/common/demo_options.h" #include "demo/common/demo_registry.h" #include "demo/common/result_writer.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/interface.h" #include "edgeflow/operator/types.h" #include "nlohmann/json.hpp" diff --git a/demo/fixtures/mock/pipeline_audio_asr_intent_cabi.json b/demo/fixtures/mock/pipeline_audio_asr_intent_cabi.json deleted file mode 100644 index ec608b09..00000000 --- a/demo/fixtures/mock/pipeline_audio_asr_intent_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_audio_asr_intent.json", - "io_binding": "audio_asr_intent.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_dialogue_audit_cabi.json b/demo/fixtures/mock/pipeline_dialogue_audit_cabi.json deleted file mode 100644 index cc37d772..00000000 --- a/demo/fixtures/mock/pipeline_dialogue_audit_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_dialogue_audit.json", - "io_binding": "compliance_audit.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_doc_qa_cabi.json b/demo/fixtures/mock/pipeline_doc_qa_cabi.json deleted file mode 100644 index 4fca4249..00000000 --- a/demo/fixtures/mock/pipeline_doc_qa_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_doc_qa.json", - "io_binding": "doc_qa.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_doc_qa_custom_cabi.json b/demo/fixtures/mock/pipeline_doc_qa_custom_cabi.json deleted file mode 100644 index 7ab3661d..00000000 --- a/demo/fixtures/mock/pipeline_doc_qa_custom_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_doc_qa_custom.json", - "io_binding": "doc_qa.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_doc_qa_rerank_cabi.json b/demo/fixtures/mock/pipeline_doc_qa_rerank_cabi.json deleted file mode 100644 index 7cd70ab5..00000000 --- a/demo/fixtures/mock/pipeline_doc_qa_rerank_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_doc_qa_rerank.json", - "io_binding": "doc_qa.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_entity_extract_cabi.json b/demo/fixtures/mock/pipeline_entity_extract_cabi.json deleted file mode 100644 index edb511c1..00000000 --- a/demo/fixtures/mock/pipeline_entity_extract_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_entity_extract.json", - "io_binding": "entity_extract.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_entity_extract_custom_cabi.json b/demo/fixtures/mock/pipeline_entity_extract_custom_cabi.json deleted file mode 100644 index ba1e2170..00000000 --- a/demo/fixtures/mock/pipeline_entity_extract_custom_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_entity_extract_custom.json", - "io_binding": "entity_extract.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_ocr_doc_qa_cabi.json b/demo/fixtures/mock/pipeline_ocr_doc_qa_cabi.json deleted file mode 100644 index b64478cb..00000000 --- a/demo/fixtures/mock/pipeline_ocr_doc_qa_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_ocr_doc_qa.json", - "io_binding": "ocr_doc_qa.cabi.v1" - } -} diff --git a/demo/fixtures/mock/pipeline_translate_cabi.json b/demo/fixtures/mock/pipeline_translate_cabi.json deleted file mode 100644 index 0feda0e3..00000000 --- a/demo/fixtures/mock/pipeline_translate_cabi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "schema_version": 1, - "data": { - "pipe_path": "pipeline_translate.json", - "io_binding": "translate.cabi.v1" - } -} diff --git a/dev_support/inference/bge_model_test_support.h b/dev_support/inference/bge_model_test_support.h index a91a036d..10c1a46a 100644 --- a/dev_support/inference/bge_model_test_support.h +++ b/dev_support/inference/bge_model_test_support.h @@ -11,7 +11,7 @@ #include #include -#include "edgeflow/c_api.h" +#include "edgeflow/operator/interface.h" #include "engine/backend_interface.h" namespace llm_edgeflow { @@ -66,7 +66,7 @@ inline Tensor MakeFaultTensor(const TensorDesc& desc, size_t byte_size, class BgeModelTestBase : public ::testing::Test { protected: void SetUp() override { - Alg_Init(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); std::random_device entropy; for (size_t attempt = 0; attempt < 32; ++attempt) { temp_dir_ = std::filesystem::temp_directory_path() / @@ -82,7 +82,7 @@ class BgeModelTestBase : public ::testing::Test { void TearDown() override { std::error_code error; std::filesystem::remove_all(temp_dir_, error); - Alg_DeInit(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); } std::filesystem::path temp_dir_; diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 290411e0..c33efd6f 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 2026-09-17 退出旧 C ABI,仅保留 C++ Operator SDK(RFC-0060) + +- **接口收口与退出旧 C ABI**: + - 彻底删除旧六函数 `Alg_*` C ABI 算法入口(`include/edgeflow/c_api.h`、`include/edgeflow/c_api.hpp`、`src/adapter/c_api_adapter.cpp`)。 + - 仅保留标准 C++ Operator API(`llm_edgeflow::operator_api`)作为唯一算法接口。 + - 删除旧平台类型声明 `include/platform_mock/alg_types.h` 及 C ABI 专属转换分支。 + - 共享库版本升级:产品版本 11.0.0,ABI major 升级为 7(`SOVERSION 7`),精确导出 6 个符号(Operator 函数表、错误查询、配置预检及 AlgBase 日志接口)。 +- **适配层视图与元数据契约收敛**: + - 移除 `ExternalInputBatchView` 中短后缀回退逻辑与模糊 `At` 入口,调用方统一使用精确逻辑槽名 `GetSlot` 访问并严格执行类型元数据检查。 + - 彻底移除旧 C ABI 遗留的 `WriteTextCarrierOutput` 辅助模板及其死路径。 + - 移除 `ExternalOutputBatchView` 中残留的旧 C ABI 批次容量字段 `capacity`。 + - 恢复 `ExternalSlotDefinition` 显式构造契约,移除对 `type_suffix` 的静默补全,确保注册中心必填校验生效。 + ## 2026-09-16 输入输出转换独立化与接入绑定架构(RFC-0059) - **输入输出转换解耦与注册中心**: diff --git a/doc/architecture.md b/doc/architecture.md index 3ddb3d0c..82ac7ce8 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -33,9 +33,8 @@ graph TD %% Integration subgraph Integration["接入适配层(Integration)"] - C_API["公司统一标准 C ABI 接口
• Alg_Init / Alg_DeInit
• Alg_Create / Alg_Destroy
• Alg_Process(const void** inputs, num_inputs, void** outputs, num_outputs)
• Alg_Control"] - C_Adapter["C ABI 外观 (c_api_adapter.cpp)
• 同句柄 Process / Control 串行化
• 异常拦截屏障 (noexcept 安全防护)
• 调用注册业务 Adapter"] - Biz_Adapter["注册业务 Adapter (IBizAdapter)
• Unpack:完整请求解析与字段转换
• Pack:完整响应组装与容量检查"] + PlatformFacade["C++ Operator 门面 (operator_adapter.cpp)
• 命名 I/O 槽位校验与 ValueType 转换
• 有界输出池租约生命周期管理
• 同句柄 Process / Control 串行化
• 异常拦截屏障 (noexcept 安全防护)"] + IoBinding["I/O 绑定与转换注册 (io_binding_registry.cpp)
• IoBindingRegistry / IoConverterRegistry
• InputConverter:完整请求解析与字段转换
• OutputConverter:完整响应组装与容量检查"] end %% Orchestration @@ -95,11 +94,10 @@ graph TD end %% 连接关系 - Caller <==|纯 C 指针数组 const void** inputs, outputs| C_API - C_API --> C_Adapter - C_Adapter -->|构造/销毁| PipeCore - C_Adapter --> Biz_Adapter - Biz_Adapter -->|解包/打包| R_Ctx + Caller <==|命名 I/O 批次 NamedIoBatch| PlatformFacade + PlatformFacade --> IoBinding + PlatformFacade -->|已验证 IoPlan 构造/控制| PipeCore + IoBinding -->|解包/打包| R_Ctx PipeCore --> S_Ctx PipeCore --> NodeApi NodeApi --> NodeBase @@ -115,7 +113,7 @@ graph TD ModelSemantics --> BatchExec class Caller ext; - class C_API,C_Adapter,Biz_Adapter integration; + class PlatformFacade,IoBinding integration; class PipeCore,S_Ctx,R_Ctx,TraceTag,Factory orchestration; class NodeApi,NodeBase,ModelNode,CommonNodes,CustomNodes,LlmNode,ChunkNode,RuleNode,EmbedNode,TopKNode,RerankNode,TemplateNode,JsonNode,AsrNode,OcrNode,CorpusNode capability_nodes; class ModelBase,BackendBase,LlmIntf,EmbedIntf,BatchExec,BgeModels,GeneratedEmbedModel,QwenModel,OnnxBackend,LlamaCpp,KiteLlm model_execution; @@ -126,41 +124,36 @@ graph TD ## 2. 职责与扩展边界 ### 接入适配层(Integration) -- **代码位置**:`include/edgeflow/c_api.h`,`include/edgeflow/operator/`,`src/adapter/` +- **代码位置**:`include/edgeflow/operator/`,`include/adapter/`,`src/adapter/` - **核心职责**: - 1. 导出公司限定的标准 C 接口:`Alg_Init`, `Alg_Create`, `Alg_Process`, `Alg_Control`, `Alg_Destroy`, `Alg_DeInit`; + 1. 导出基于命名 I/O 槽位的 C++ Operator 门面:`Get_LLM_EDGEFLOW_OperatorTable()`, `GetOperatorLastError()`, `ValidateOperatorConfigBinding()`; 2. 导出公共日志 C API:`AlgBase_setLogLevelByName`, `AlgBase_getLogLevelByName`, `AlgBase_logPrint`; - 3. 导出基于命名 I/O 槽位的 C++ Operator 门面:`Get_LLM_EDGEFLOW_OperatorTable()`, `GetOperatorLastError()`, `ValidateOperatorConfigBinding()`; - 4. 充当 `noexcept` 安全屏障,拦截所有 C++ 异常,防止跨动态库边界崩溃; - 5. 由注册业务 Adapter 解包完整外部请求并组装完整外部响应,负责外部契约与内部 - `AlgContext` 中性值之间的转换;Operator bridge 负责宿主载体转换。 + 3. 充当 `noexcept` 安全屏障,拦截所有 C++ 异常,防止跨动态库边界崩溃; + 4. 由注册的 Input/Output Converter 与 IoBinding 解包完整外部请求并组装完整外部响应,负责外部契约与内部 `AlgContext` 中性值之间的转换; + 5. 管理有界输出池与租约生命周期,执行同句柄 Process/Control 串行化。 -业务需求中的输入输出以 C ABI 边界为准,包含载体中的业务字段和序列化格式。 +业务需求中的输入输出以 Operator 接口边界为准,包含载体中的业务字段和序列化格式。 Demo 不得提前拆解请求或在 SDK 返回后补组业务响应;内部节点端口不是外部 I/O 契约。 -具体职责和判断示例见[输入输出边界](dev_guide/business_onboarding.md#输入输出以-c-abi-为边界)。 +具体职责和判断示例见[输入输出边界](dev_guide/business_onboarding.md)。 -#### 双外部门面与单一内部运行时架构 +#### 统一 Operator 门面与运行时架构 -接入适配层并行维护两个外部门面,统一由 `SharedAlgorithmRuntime` 执行调度: +接入适配层通过标准 C++ Operator 门面与共享算法运行时调度算法执行: ```text -纯 C ABI:const void** / void** + 现有 CompanyAlg DTO ─┐ - ├─> SharedAlgorithmRuntime -C++ Operator API:NamedIoBatch + Operator 镜像 C 结构 ─┘ +外部调用方 (NamedIoBatch) ──> OperatorFunc::Process ──> InputConverter ──> SharedAlgorithmRuntime + │ + ▼ + Pipeline (DAG) + │ + ▼ +外部调用方 (获取已租用输出) <── 发布输出 <── OutputConverter <── 执行完成后 ``` -- 纯 C ABI 继续保持 C11、固定布局和现有六函数契约,当前 ABI 版本见下文。 -- v10.0.0 / ABI 5 只承诺上述 12 个动态入口;Node、Registry、Model、Backend 及第三方 - 运行时符号使用 hidden visibility,不构成稳定动态 ABI。 -- 同一 C ABI handle 的 `Alg_Process` 与 `Alg_Control` 串行执行;不同 handle 可并行。 - `Alg_Destroy` 前调用方必须停止提交并等待该 handle 上所有调用返回,返回后句柄永久失效。 -- C++ Operator API 根据 Key 的最后一个点号解析槽位后缀: - `OperatorValueTypeRegistry` 负责“后缀到外部 C 类型”的唯一绑定, - `OperatorBizBridgeDescriptor` 负责按业务和方向收集一个或多个槽位,再转换为 - 内部 DTO;输出槽位须显式指定 `key_suffix`(单槽 Helper 默认填充为规范类型后缀),与逻辑槽及类型解耦,可独立命名;描述符中已不再支持运行时省略或隐式回退。 - 同一外层类型可以注册多个分配方案;部署配置选择方案与嵌套布局参数。 - 两种协议不得通过 `reinterpret_cast` 混用布局。 -- 组件调用关系:`外部调用方 → Operator / C ABI → Pipeline → Node → Model → Backend → Platform`。 +- 标准 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 上所有调用返回,释放全部输出指针引用,返回后句柄永久失效。 +- C++ Operator API 根据 Key 的最后一个点号解析槽位后缀:`OperatorValueTypeRegistry` 负责“后缀到外部 C++ 类型”的唯一绑定;`IoBindingRegistry` 负责按业务和方向将外部命名槽位映射到内部 Pipeline 逻辑端口。 +- 组件调用关系:`外部调用方 → Operator → Pipeline → Node → Model → Backend → Platform`。 `Operator` 表达对外交付的算法实例,`Platform`(`ComputePlatform`)表达底层硬件执行平台(CPU、CUDA、AX650、Ascend 等)。 - 同一业务可以使用一个聚合结构槽位,也可以由多个原子槽位组成;支持多槽位解绑。 - `CompanyString` 只表达无嵌入 NUL 的文本;任意二进制数据使用 `CompanyBuffer`。 @@ -169,8 +162,8 @@ C++ Operator API:NamedIoBatch + Operator 镜像 C 结构 ─┘ deleter 的 shared_ptr,最后一个引用析构后 reset 并回池;deleter 只捕获池状态的 weak lifetime token,避免 Destroy 后解引用已释放句柄或池。 - 值类型表、业务桥接表和内存池只属于接入适配层,不得进入 Blackboard、Node、Model 或 Backend。 -- 目标共享库输出名称为 `company_alg_sdk`,产品 VERSION 为 10.0.0, - SOVERSION/C ABI major 为 6。 +- 目标共享库输出名称为 `company_alg_sdk`,产品 VERSION 为 11.0.0, + SOVERSION/ABI major 为 7。 - v4 Create 和配置预检都以必填部署根 `model_path` 加相对 `cfg_file_name` 解析; `.conf` 的 `data.outputs` 按逻辑槽位归一化输出类型、分配方案、参数与容量; 最外层的独立配置读取组件按固定枚举提取配置并返回字符串,注册方案在 Create diff --git a/doc/architecture.puml b/doc/architecture.puml index 49226b13..d27f9457 100644 --- a/doc/architecture.puml +++ b/doc/architecture.puml @@ -42,45 +42,49 @@ skinparam arrow { ' Integration ' ============================================================================== package "接入适配层(Integration)" as Integration { - class "Company C ABI Exports" as C_ABI <<(C,#0284C7) API Export>> { - + Alg_Init(): int noexcept - + Alg_Create(void** hndl, const CompanyAlgParamCreate*): int noexcept - + Alg_Process(void* hndl, const void** in, int num_in, void** out, int* num_out): int noexcept - + Alg_Control(void* hndl, const CompanyAlgParamControl*): int noexcept - + Alg_Destroy(void* hndl): int noexcept - + Alg_DeInit(): int noexcept - } - - class "Operator Facade" as PlatformFacade <<(P,#38BDF8) Operator API>> { + class "Operator SDK Entrypoint" as PlatformFacade <<(P,#38BDF8) Operator API>> { + Get_LLM_EDGEFLOW_OperatorTable(): OperatorFunc + + GetOperatorLastError(): string + + ValidateOperatorConfigBinding(cfg_path, model_root, err): int + OperatorConfigResolver: resolve .conf & rewrite model paths + OperatorValueTypeRegistry: value type whitelist & pool sizing - + OperatorBizBridgeRegistry: biz bridge slots & conversion + OperatorControlRegistry: typed control commands } + class "OperatorHandleInstance" as HandleInst { + + runtime: std::unique_ptr + + input_converter: std::shared_ptr + + output_converter: std::shared_ptr + + output_pools: std::unordered_map + + mutex: std::mutex + } + class "SharedAlgorithmRuntime" as SharedRuntime <<(R,#10B981) Shared Runtime>> { - pipeline_: std::unique_ptr - - adapter_: std::shared_ptr - + ExecuteBatch(inputs, num_inputs, outputs, num_outputs, err): int + - io_plan_: std::unique_ptr + + CreateFromIoPlan(plan, runtime_options, err): unique_ptr + ExecuteControl(cmd, json_param, err): int } - class "AlgHandleInstance" as HandleInst { - + runtime: std::unique_ptr + class "IoBindingRegistry" as BindingRegistry <<(S,#EC4899) Singleton>> { + + RegisterBinding(definition: IoBindingDefinition): bool + + FindBinding(binding_id: string): const IoBindingDefinition* + + HasConflict(): bool } - class "BizAdapterRegistry" as AdapterRegistry <<(S,#EC4899) Singleton>> { - + RegisterAdapter(adapter: shared_ptr): bool - + GetAdapter(biz_type: CompanyAlgBizType): std::shared_ptr - + HasConflict(): bool + class "IoConverterRegistry" as ConverterRegistry <<(S,#EC4899) Singleton>> { + + RegisterInputConverter(definition, factory): bool + + RegisterOutputConverter(definition, factory): bool + + CreateInputConverter(converter_id): unique_ptr + + CreateOutputConverter(converter_id): unique_ptr + } + + interface "InputConverter" as IInputConverter { + + {abstract} Decode(view: const InputView&, ctx: AlgContext*, status: AdapterStatus*): int } - interface "IBizAdapter" as IAdapter { - + {abstract} GetDescriptor(): const AdapterDescriptor& - + {abstract} ValidateBatch(in, num_in, out, num_out): int - + {abstract} Unpack(in, num_in, ctx, status): int - + {abstract} Pack(ctx, out, num_out, status): int + interface "OutputConverter" as IOutputConverter { + + {abstract} Encode(ctx: const AlgContext&, view: OutputView*, status: AdapterStatus*): int } class "AdapterValidationHelper" as ValidationHelper { @@ -88,14 +92,11 @@ package "接入适配层(Integration)" as Integration { + {static} RequireBoundedString(str, max_len, status): bool } - class "Multi-Modal C Structs" as Structs <<(S,#F59E0B) Data Struct>> { - + CompanyDocInput / Output (Biz 1) - + CompanyKeywordInput / Output (Biz 2) - + CompanyEntityInput / Output (Biz 3) - + CompanyAuditInput / Output (Biz 4) - + CompanyOcrDocInput / Output (Biz 5) - + CompanyAudioInput / Output (Biz 6) - + CompanyRerankBatchInput / Output (Biz 7) + class "Operator Named I/O & Types" as Structs <<(S,#F59E0B) Data Struct>> { + + CompanyString / CompanyBuffer / CompanyAny + + NamedIo / NamedIoBatch + + CompanyDocQA / Keyword / Entity / Audit + + CompanyAudio / Invoice / Rerank Operator Types } } @@ -302,14 +303,17 @@ package "Platform Tooling & Visualizer Studio (Tools & Demo)" as Tools { ' ============================================================================== ' 关系连接与流向 ' ============================================================================== -C_ABI ..> HandleInst : 管理生命周期 -C_ABI ..> Structs : 解包/填充 -PlatformFacade ..> SharedRuntime : 命名 I/O 适配 +PlatformFacade ..> HandleInst : 管理生命周期与 Process/Control +PlatformFacade ..> Structs : 命名槽位校验与租约 HandleInst *-- SharedRuntime +HandleInst o-- IInputConverter +HandleInst o-- IOutputConverter SharedRuntime *-- Pipeline -SharedRuntime o-- AdapterRegistry -AdapterRegistry o-- IAdapter -IAdapter ..> ValidationHelper : 严格容量与截断防护 +BindingRegistry ..> ConverterRegistry : 关联输入/输出转换器 +ConverterRegistry o-- IInputConverter +ConverterRegistry o-- IOutputConverter +IInputConverter ..> ValidationHelper : 字段截断与容量防护 +IOutputConverter ..> ValidationHelper : 字段截断与容量防护 Pipeline *-- SessionContext Pipeline *-- INode : 显式 DAG 拓扑执行 diff --git a/doc/architecture_v2.puml b/doc/architecture_v2.puml index fd8f32b8..73f2fb62 100644 --- a/doc/architecture_v2.puml +++ b/doc/architecture_v2.puml @@ -80,24 +80,23 @@ Validator --> BizTests : validated package ' Integration package "接入适配层(Integration)" as Integration { - component "Pure C ABI Facade\n6 lifecycle APIs\nnoexcept exception firewall" as CAbi <> - component "Operator Facade\nOperatorFunc + NamedIoBatch\nOperatorConfigResolver\nOperatorBizBridgeRegistry" as PlatformFacade <> - component "Shared Algorithm Runtime\nSharedAlgorithmRuntime\nSingle Unpack-Execute-Pack" as SharedRuntime - artifact "Business-Specific\nPure C Structs" as CStructs - component "BizAdapterRegistry" as AdapterRegistry - interface "IBizAdapter\nDescriptor / ValidateBatch\nUnpack / Pack" as IBizAdapter - component "Biz Adapters\nStatically Linked" as BizAdapters + component "Operator Facade\nOperatorFunc + NamedIoBatch\nOperatorConfigResolver\nOperatorControlRegistry" as PlatformFacade <> + component "Shared Algorithm Runtime\nSharedAlgorithmRuntime\nSingle Decode-Execute-Encode" as SharedRuntime + artifact "Business-Specific\nOperator Types" as CStructs + component "IoBindingRegistry" as AdapterRegistry + interface "IoBinding\nInputConverter / OutputConverter\nDecode / Encode" as IBizAdapter + component "I/O Converters\nStatically Linked" as BizAdapters artifact "Common Typed Contracts" as CommonContracts } -Caller --> CAbi : pure C calls +Caller --> PlatformFacade : OperatorFunc calls PlatformScheduler --> PlatformFacade : OperatorFunc calls -PlatformFacade --> SharedRuntime : convert NamedIo to C ptrs -CAbi --> SharedRuntime : forward C params -SharedRuntime --> AdapterRegistry : dispatch by business_id / name +PlatformFacade --> SharedRuntime : execute via IoPlan +PlatformFacade --> BizAdapters : decode input / encode output +SharedRuntime --> AdapterRegistry : resolve binding by config AdapterRegistry o-- IBizAdapter BizAdapters ..|> IBizAdapter -BizAdapters --> CStructs : validate / unpack / pack +BizAdapters --> CStructs : decode / encode slots BizAdapters --> CommonContracts : map to typed values Manifest --> AdapterRegistry : registration metadata Scaffolder --> BizAdapters : generate scaffold @@ -211,10 +210,10 @@ package "Cross-Cutting Platform Contracts" as CrossCutting { } Versioning ..> Manifest -Versioning ..> CAbi +Versioning ..> PlatformFacade Versioning ..> NodeDescriptor Versioning ..> InferenceDescriptors -Observability ..> CAbi +Observability ..> PlatformFacade Observability ..> PipelineRuntime Observability ..> IModel Observability ..> IInferenceBackend diff --git a/doc/assets/architecture_class_diagram.svg b/doc/assets/architecture_class_diagram.svg index bd276cad..a4e8aeeb 100644 --- a/doc/assets/architecture_class_diagram.svg +++ b/doc/assets/architecture_class_diagram.svg @@ -1,2 +1,2 @@ - -接入适配层(Integration)流程编排层(Orchestration)能力节点层(Capability Nodes)模型执行层(Model Execution)Platform Tooling & Visualizer Studio (Tools & Demo)PipelineConfig«API Export»Company C ABI ExportsAlg_Init(): int noexceptAlg_Create(void** hndl, const CompanyAlgParamCreate*): int noexceptAlg_Process(void* hndl, const voidin, int num_in, voidout, int* num_out): int noexceptAlg_Control(void* hndl, const CompanyAlgParamControl*): int noexceptAlg_Destroy(void* hndl): int noexceptAlg_DeInit(): int noexcept«Operator API»Operator FacadeOperatorConfigResolver: resolve .conf & rewrite model pathsOperatorValueTypeRegistry: value type whitelist & pool sizingOperatorBizBridgeRegistry: biz bridge slots & conversionOperatorControlRegistry: typed control commandsGet_LLM_EDGEFLOW_OperatorTable(): OperatorFunc«Shared Runtime»SharedAlgorithmRuntimepipeline_: std::unique_ptr<Pipeline>adapter_: std::shared_ptr<IBizAdapter>ExecuteBatch(inputs, num_inputs, outputs, num_outputs, err): intExecuteControl(cmd, json_param, err): intAlgHandleInstanceruntime: std::unique_ptr<SharedAlgorithmRuntime>«Singleton»BizAdapterRegistryRegisterAdapter(adapter: shared_ptr<IBizAdapter>): boolGetAdapter(biz_type: CompanyAlgBizType): std::shared_ptr<IBizAdapter>HasConflict(): boolIBizAdapterGetDescriptor(): const AdapterDescriptor&ValidateBatch(in, num_in, out, num_out): intUnpack(in, num_in, ctx, status): intPack(ctx, out, num_out, status): intAdapterValidationHelperCheckedStringCopy(src, dst, max_len, status): boolRequireBoundedString(str, max_len, status): bool«Data Struct»Multi-Modal C StructsCompanyDocInput / Output (Biz 1)CompanyKeywordInput / Output (Biz 2)CompanyEntityInput / Output (Biz 3)CompanyAuditInput / Output (Biz 4)CompanyOcrDocInput / Output (Biz 5)CompanyAudioInput / Output (Biz 6)CompanyRerankBatchInput / Output (Biz 7)Pipelinenodes_: std::vector<std::unique_ptr<INode>>session_context_: SessionContextplan_: std::unique_ptr<ValidatedPipelinePlan>state_: State {kEmpty, kBuilding, kReady, kFailed}execution_layers_: std::vector<std::vector<size_t>>BuildFromConfigFile(config_path: string): boolBuildFromJson(root_config: json): boolExecute(req_ctx: AlgContext*): intControl(cmd: int, json_param: string): int«Static Validator»PipelineValidatorValidateAndPlan(json_obj, policy): ValidatedPipelinePlanValidate(json_obj, policy): ValidationReport«Immutable Plan»ValidatedPipelinePlanreport: ValidationReportconfig: ParsedPipelineConfigtopological_order: vector<string>topological_layers: vector<vector<string>>«Request Blackboard»AlgContextblackboard_: std::unordered_map<string, std::any>mutable rw_mutex_: std::shared_mutexerror_code_: interror_message_: std::stringPublish<T>(key: BlackboardKey<T>, value: T): boolRead<T>(key: BlackboardKey<T>): const T*Has<T>(key: BlackboardKey<T>): boolSetError(code: int, msg: string): voidSessionContextmodel_manager_: ModelManagerruntime_options_: RuntimeOptionsGetModelManager(): ModelManager&GetRuntimeOptions(): const RuntimeOptions&ModelManagermodels_: map<string, ModelRegistration>RegisterBatch(models): boolGetModel<T>(id: string): std::shared_ptr<T>HasModel(id: string): bool«Data Container»TraceableItemTreq_id: uint64_tsub_id: uint32_tdata: TINodeInit(init_ctx: const NodeInitContext&): boolProcess(req_ctx: AlgContext*): intControl(cmd: int, json_param: const string&): intName(): const string&«Base Class»NodeBaseProcessNode(req_ctx: AlgContext&): intInitNode(init_ctx, config, session_ctx): boolRequire<T>(ctx, key, err_code): const T*Publish<T>(ctx, key, val): voidFail(ctx, err_code, msg): int«Model Template»ModelBoundNodeEmodel(): shared_ptr<E>model_id(): const string&«Unary Template»TraceableUnaryInferenceNodeE, In, OutInferBatch(in: InputBatch, out: OutputBatch*): int«Singleton»NodeRegistryInstance(): NodeRegistry&Register(type: string, creator: Func): voidCreate(type: string): std::unique_ptr<INode>HasConflict(): boolRegistered Common Nodes(query PipelineCatalog for exact list)IModelModelType(): const string&Capability(): const string&Concurrency(): InferenceConcurrencyGetMaxBatchSize(): size_tIEmbeddingModelEmbed(inputs: TextBatch, options, outputs): intIRerankModelScore(inputs: QueryCandidatesBatch, outputs: ScoreBatch*): intILlmModelGenerate(prompts: TextBatch, options, outputs: TextBatch*): intIOcrModelRecognize(images: ImageRefBatch, outputs: OcrDocumentBatch*): intIAsrModelTranscribe(audio: AudioPcmBatch, outputs: TextBatch*): intIBackendSessionBackendType(): const string&Protocol(): ExecutionProtocolConcurrency(): InferenceConcurrencyGetBatchPolicy(): BatchPolicyITensorGraphSessionInputs(): vector<TensorSpec>Outputs(): vector<TensorSpec>Run(inputs: TensorMap, outputs: TensorMap*): intITextGenerationSessionGenerate(formatted_prompt, add_bos, options, seed, output): int«Backend Private»IAutoregressiveDecoderCommonAutoregressiveGeneratorIInferenceBackendBackendType(): const string&Load(spec: BackendLoadSpec): shared_ptr<IBackendSession>«Singleton»ModelRegistryRegister(definition: ModelDefinition, creator): boolCreate(model_type: string, context): shared_ptr<IModel>«Singleton»BackendRegistryRegister(definition: BackendDefinition, creator): boolCreate(backend_type: string): unique_ptr<IInferenceBackend>ModelRuntimeFactoryCreate(config, runtime_options): ModelRegistrationBgeEmbeddingModelBgeRerankerModelQwenCausalLmModelOnnxRuntimeBackendLlamaCppBackendKiteLlmBackend«Generic Template»FixedBatchExecutorExecute<TIn, TOut>(inputs, policy: BatchPolicy, callback: BatchSlice, outputs): int«C++ Tool»alg_pipeline_tool (CLI)catalog / describe-node / init / validate / plan«C++ Tool»alg_show (CLI)Native declared id / depends_on print«Web App»Web Visualizer Studio./show --web (ES Modules + SVG Graph + Live Demo Draft)«Demo Suite»Parameterized Demo Runnerprofiles.json (Single source of truth)OperatorRunner (RAII lifecycle execution)ResultWriter (JSONL / Summary persistence)管理生命周期解包/填充命名 I/O 适配严格容量与截断防护显式 DAG 拓扑执行强类型解析静态校验与规范化动态反射创建 (Fail-Closed)Typed Key 读写索取纯虚能力接口创建中性协议会话公共采样生成托管生成仅依赖 Tensor 协议仅依赖 Tensor 协议已格式化 prompt / 统一参数backend 创建与加载model 语义对象创建自描述注册 (Fail-Closed)自描述注册 (Fail-Closed)定长分批与溯源定长分批与溯源批次切分与溯源样本切块/补齐/剥离/溯源交互式编排调用驱动 Demo 与 Operator API静态校验与拓扑规划 \ No newline at end of file + +接入适配层(Integration)流程编排层(Orchestration)能力节点层(Capability Nodes)模型执行层(Model Execution)Platform Tooling & Visualizer Studio (Tools & Demo)PipelineConfig«Operator API»Operator SDK EntrypointOperatorConfigResolver: resolve .conf & rewrite model pathsOperatorValueTypeRegistry: value type whitelist & pool sizingOperatorControlRegistry: typed control commandsGet_LLM_EDGEFLOW_OperatorTable(): OperatorFuncGetOperatorLastError(): stringValidateOperatorConfigBinding(cfg_path, model_root, err): intOperatorHandleInstanceruntime: std::unique_ptr<SharedAlgorithmRuntime>input_converter: std::shared_ptr<InputConverter>output_converter: std::shared_ptr<OutputConverter>output_pools: std::unordered_map<string, OutputPool>mutex: std::mutex«Shared Runtime»SharedAlgorithmRuntimepipeline_: std::unique_ptr<Pipeline>io_plan_: std::unique_ptr<ValidatedIoPlan>CreateFromIoPlan(plan, runtime_options, err): unique_ptr<SharedAlgorithmRuntime>ExecuteControl(cmd, json_param, err): int«Singleton»IoBindingRegistryRegisterBinding(definition: IoBindingDefinition): boolFindBinding(binding_id: string): const IoBindingDefinition*HasConflict(): bool«Singleton»IoConverterRegistryRegisterInputConverter(definition, factory): boolRegisterOutputConverter(definition, factory): boolCreateInputConverter(converter_id): unique_ptr<InputConverter>CreateOutputConverter(converter_id): unique_ptr<OutputConverter>InputConverterDecode(view: const InputView&, ctx: AlgContext*, status: AdapterStatus*): intOutputConverterEncode(ctx: const AlgContext&, view: OutputView*, status: AdapterStatus*): intAdapterValidationHelperCheckedStringCopy(src, dst, max_len, status): boolRequireBoundedString(str, max_len, status): bool«Data Struct»Operator Named I/O & TypesCompanyString / CompanyBuffer / CompanyAnyNamedIo / NamedIoBatchCompanyDocQA / Keyword / Entity / AuditCompanyAudio / Invoice / Rerank Operator TypesPipelinenodes_: std::vector<std::unique_ptr<INode>>session_context_: SessionContextplan_: std::unique_ptr<ValidatedPipelinePlan>state_: State {kEmpty, kBuilding, kReady, kFailed}execution_layers_: std::vector<std::vector<size_t>>BuildFromConfigFile(config_path: string): boolBuildFromJson(root_config: json): boolExecute(req_ctx: AlgContext*): intControl(cmd: int, json_param: string): int«Static Validator»PipelineValidatorValidateAndPlan(json_obj, policy): ValidatedPipelinePlanValidate(json_obj, policy): ValidationReport«Immutable Plan»ValidatedPipelinePlanreport: ValidationReportconfig: ParsedPipelineConfigtopological_order: vector<string>topological_layers: vector<vector<string>>«Request Blackboard»AlgContextblackboard_: std::unordered_map<string, std::any>mutable rw_mutex_: std::shared_mutexerror_code_: interror_message_: std::stringPublish<T>(key: BlackboardKey<T>, value: T): boolRead<T>(key: BlackboardKey<T>): const T*Has<T>(key: BlackboardKey<T>): boolSetError(code: int, msg: string): voidSessionContextmodel_manager_: ModelManagerruntime_options_: RuntimeOptionsGetModelManager(): ModelManager&GetRuntimeOptions(): const RuntimeOptions&ModelManagermodels_: map<string, ModelRegistration>RegisterBatch(models): boolGetModel<T>(id: string): std::shared_ptr<T>HasModel(id: string): bool«Data Container»TraceableItemTreq_id: uint64_tsub_id: uint32_tdata: TINodeInit(init_ctx: const NodeInitContext&): boolProcess(req_ctx: AlgContext*): intControl(cmd: int, json_param: const string&): intName(): const string&«Base Class»NodeBaseProcessNode(req_ctx: AlgContext&): intInitNode(init_ctx, config, session_ctx): boolRequire<T>(ctx, key, err_code): const T*Publish<T>(ctx, key, val): voidFail(ctx, err_code, msg): int«Model Template»ModelBoundNodeEmodel(): shared_ptr<E>model_id(): const string&«Unary Template»TraceableUnaryInferenceNodeE, In, OutInferBatch(in: InputBatch, out: OutputBatch*): int«Singleton»NodeRegistryInstance(): NodeRegistry&Register(type: string, creator: Func): voidCreate(type: string): std::unique_ptr<INode>HasConflict(): boolRegistered Common Nodes(query PipelineCatalog for exact list)IModelModelType(): const string&Capability(): const string&Concurrency(): InferenceConcurrencyGetMaxBatchSize(): size_tIEmbeddingModelEmbed(inputs: TextBatch, options, outputs): intIRerankModelScore(inputs: QueryCandidatesBatch, outputs: ScoreBatch*): intILlmModelGenerate(prompts: TextBatch, options, outputs: TextBatch*): intIOcrModelRecognize(images: ImageRefBatch, outputs: OcrDocumentBatch*): intIAsrModelTranscribe(audio: AudioPcmBatch, outputs: TextBatch*): intIBackendSessionBackendType(): const string&Protocol(): ExecutionProtocolConcurrency(): InferenceConcurrencyGetBatchPolicy(): BatchPolicyITensorGraphSessionInputs(): vector<TensorSpec>Outputs(): vector<TensorSpec>Run(inputs: TensorMap, outputs: TensorMap*): intITextGenerationSessionGenerate(formatted_prompt, add_bos, options, seed, output): int«Backend Private»IAutoregressiveDecoderCommonAutoregressiveGeneratorIInferenceBackendBackendType(): const string&Load(spec: BackendLoadSpec): shared_ptr<IBackendSession>«Singleton»ModelRegistryRegister(definition: ModelDefinition, creator): boolCreate(model_type: string, context): shared_ptr<IModel>«Singleton»BackendRegistryRegister(definition: BackendDefinition, creator): boolCreate(backend_type: string): unique_ptr<IInferenceBackend>ModelRuntimeFactoryCreate(config, runtime_options): ModelRegistrationBgeEmbeddingModelBgeRerankerModelQwenCausalLmModelOnnxRuntimeBackendLlamaCppBackendKiteLlmBackend«Generic Template»FixedBatchExecutorExecute<TIn, TOut>(inputs, policy: BatchPolicy, callback: BatchSlice, outputs): int«C++ Tool»alg_pipeline_tool (CLI)catalog / describe-node / init / validate / plan«C++ Tool»alg_show (CLI)Native declared id / depends_on print«Web App»Web Visualizer Studio./show --web (ES Modules + SVG Graph + Live Demo Draft)«Demo Suite»Parameterized Demo Runnerprofiles.json (Single source of truth)OperatorRunner (RAII lifecycle execution)ResultWriter (JSONL / Summary persistence)管理生命周期与 Process/Control命名槽位校验与租约关联输入/输出转换器字段截断与容量防护字段截断与容量防护显式 DAG 拓扑执行强类型解析静态校验与规范化动态反射创建 (Fail-Closed)Typed Key 读写索取纯虚能力接口创建中性协议会话公共采样生成托管生成仅依赖 Tensor 协议仅依赖 Tensor 协议已格式化 prompt / 统一参数backend 创建与加载model 语义对象创建自描述注册 (Fail-Closed)自描述注册 (Fail-Closed)定长分批与溯源定长分批与溯源批次切分与溯源样本切块/补齐/剥离/溯源交互式编排调用驱动 Demo 与 Operator API静态校验与拓扑规划 \ No newline at end of file diff --git a/doc/assets/architecture_flow.svg b/doc/assets/architecture_flow.svg index 81c6c6d7..8d7dbbc5 100644 --- a/doc/assets/architecture_flow.svg +++ b/doc/assets/architecture_flow.svg @@ -1,2 +1,2 @@ - -LLM-EdgeFlow Target Architecture V2LLM-EdgeFlow Target Architecture V2Developer-Friendly & AI-Friendly Business Delivery PlatformStateMeaningImplementedimplemented and covered by repository testsPartialimplemented core, remaining RFC acceptance workPlannednot implemented in RFC 0008Control Plane: Business Development Surface接入适配层(Integration)流程编排层(Orchestration)能力节点层(Capability Nodes)模型执行层(Model Execution)Cross-Cutting Platform ContractsDownstream C CallerPlatform SchedulerPipeline ComposerBusiness DeveloperAI AgentPlatform DeveloperRequest state only in RequestContextNo Integration C structsNo concrete Backend implementationsRequires RFC justificationPlatform-owned extension surfaceBusiness developers do not modify by defaultPhase 1 packaging:single repository + static linkingno dynamic plugin ABI yet«Planned»Business ManifestJSON Schema«Implemented»Pipeline ConfigExplicit DAG JSON«Implemented»Node / Model / Backend / Business CatalogPipelineCatalog (JSON Export)«Planned»Business ScaffolderTemplates«Partial»Static ValidatorPipelineValidator«Implemented»Business Test HarnessGoogleTest Suites / CTest«C ABI»Pure C ABI Facade6 lifecycle APIsnoexcept exception firewall«Operator API»Operator FacadeOperatorFunc + NamedIoBatchOperatorConfigResolverOperatorBizBridgeRegistryShared Algorithm RuntimeSharedAlgorithmRuntimeSingle Unpack-Execute-PackBusiness-SpecificPure C StructsBizAdapterRegistryIBizAdapterDescriptor / ValidateBatchUnpack / PackBiz AdaptersStatically LinkedCommon Typed ContractsPipelineBuilderPipeline::BuildFromJsonPipelineStaticValidatorPipelineValidatorDAG / ports / producers / parallel conflictsPipeline Runtimesequential / parallel wavefront«Implemented»Typed Port AccessBlackboardKey<T>«Implemented»Request BlackboardAlgContextSessionContextModelManagerRuntimeOptionsmodel root / device / limitsStructured StatusPipelineDiagnostic / AdapterStatusINodeInit / Process / Control / Name«Implemented»NodeBasefinal noexcept lifecycle«Implemented»ModelBoundNode / TraceableUnaryInferenceNodeNodeRegistry(NodeRegistry)RegisteredCommon NodesOptional Domain Node(none currently registered)NodeDescriptor(NodeDefinition in Catalog)IModelIEmbeddingModel / IRerankModel / ILlmModelIOcrModel / IAsrModelIBackendSessionTensorGraph / TextGeneration protocolsIInferenceBackendloads backend sessionsModelRegistryBackendRegistryModelRuntimeFactorycompose model_type + backendFixedBatchExecutorchunk / pad / stripreq_id / sub_id provenanceBGE Embedding / Reranker ModelsQwen Text Generation ModelONNX Runtime Backendllama.cpp BackendkiteLLM Backend(conditional SDK)Future Hardware Backends«Implemented»ModelDefinition / BackendDefinitionprotocol + concurrency contractsVersion & CompatibilityObservabilityLogs / Metrics / TracesLayer Dependency GuardOwnership & Review Policycomposecreate businessgenerate scaffoldgenerate / modifypublish capabilitiesgenerategeneratevalidated packagepure C callsOperatorFunc callsconvert NamedIo to C ptrsforward C paramsdispatch by business_id / namevalidate / unpack / packmap to typed valuesregistration metadatagenerate scaffoldvalidated definitionsexecutable planstructured failuresinject / extractregistration metadatacreate nodesdeclared access onlymodels / resourcescomposition contract testsloadmodel metadatabackend metadatacreate modelscapability interfaces onlyTensorGraph protocolTextGeneration protocol \ No newline at end of file + +LLM-EdgeFlow Target Architecture V2LLM-EdgeFlow Target Architecture V2Developer-Friendly & AI-Friendly Business Delivery PlatformStateMeaningImplementedimplemented and covered by repository testsPartialimplemented core, remaining RFC acceptance workPlannednot implemented in RFC 0008Control Plane: Business Development Surface接入适配层(Integration)流程编排层(Orchestration)能力节点层(Capability Nodes)模型执行层(Model Execution)Cross-Cutting Platform ContractsDownstream C CallerPlatform SchedulerPipeline ComposerBusiness DeveloperAI AgentPlatform DeveloperRequest state only in RequestContextNo Integration C structsNo concrete Backend implementationsRequires RFC justificationPlatform-owned extension surfaceBusiness developers do not modify by defaultPhase 1 packaging:single repository + static linkingno dynamic plugin ABI yet«Planned»Business ManifestJSON Schema«Implemented»Pipeline ConfigExplicit DAG JSON«Implemented»Node / Model / Backend / Business CatalogPipelineCatalog (JSON Export)«Planned»Business ScaffolderTemplates«Partial»Static ValidatorPipelineValidator«Implemented»Business Test HarnessGoogleTest Suites / CTest«Operator API»Operator FacadeOperatorFunc + NamedIoBatchOperatorConfigResolverOperatorControlRegistryShared Algorithm RuntimeSharedAlgorithmRuntimeSingle Decode-Execute-EncodeBusiness-SpecificOperator TypesIoBindingRegistryIoBindingInputConverter / OutputConverterDecode / EncodeI/O ConvertersStatically LinkedCommon Typed ContractsPipelineBuilderPipeline::BuildFromJsonPipelineStaticValidatorPipelineValidatorDAG / ports / producers / parallel conflictsPipeline Runtimesequential / parallel wavefront«Implemented»Typed Port AccessBlackboardKey<T>«Implemented»Request BlackboardAlgContextSessionContextModelManagerRuntimeOptionsmodel root / device / limitsStructured StatusPipelineDiagnostic / AdapterStatusINodeInit / Process / Control / Name«Implemented»NodeBasefinal noexcept lifecycle«Implemented»ModelBoundNode / TraceableUnaryInferenceNodeNodeRegistry(NodeRegistry)RegisteredCommon NodesOptional Domain Node(none currently registered)NodeDescriptor(NodeDefinition in Catalog)IModelIEmbeddingModel / IRerankModel / ILlmModelIOcrModel / IAsrModelIBackendSessionTensorGraph / TextGeneration protocolsIInferenceBackendloads backend sessionsModelRegistryBackendRegistryModelRuntimeFactorycompose model_type + backendFixedBatchExecutorchunk / pad / stripreq_id / sub_id provenanceBGE Embedding / Reranker ModelsQwen Text Generation ModelONNX Runtime Backendllama.cpp BackendkiteLLM Backend(conditional SDK)Future Hardware Backends«Implemented»ModelDefinition / BackendDefinitionprotocol + concurrency contractsVersion & CompatibilityObservabilityLogs / Metrics / TracesLayer Dependency GuardOwnership & Review Policycomposecreate businessgenerate scaffoldgenerate / modifypublish capabilitiesgenerategeneratevalidated packageOperatorFunc callsOperatorFunc callsexecute via IoPlandecode input / encode outputresolve binding by configdecode / encode slotsmap to typed valuesregistration metadatagenerate scaffoldvalidated definitionsexecutable planstructured failuresinject / extractregistration metadatacreate nodesdeclared access onlymodels / resourcescomposition contract testsloadmodel metadatabackend metadatacreate modelscapability interfaces onlyTensorGraph protocolTextGeneration protocol \ No newline at end of file diff --git a/doc/dev_guide/business_onboarding.md b/doc/dev_guide/business_onboarding.md index 8ac32534..1787be65 100644 --- a/doc/dev_guide/business_onboarding.md +++ b/doc/dev_guide/business_onboarding.md @@ -4,24 +4,24 @@ [README](../../README.md#快速开始)完成默认构建;以下命令都从仓库根目录执行。 公共结构或协议变更先按 [CONTRIBUTING](../../CONTRIBUTING.md)记录 RFC,再开始实现。 -## 输入输出以 C ABI 为边界 +## 输入输出以 Operator 接口为边界 -本项目业务需求中的“输入、输出”指 `Alg_Process` 边界上的完整请求和完整响应。 +本项目业务需求中的“输入、输出”指 `OperatorFunc::Process` 边界上的完整请求和完整响应。 字段名、字段类型、序列化格式及忽略字段的约定都属于外部业务契约。 -例如输入是 `{"query":"hello","src_lan":"en"}` 时,C ABI 输入结构的字符串字段应 +例如输入是 `{"query":"hello","src_lan":"en"}` 时,Operator 输入结构的字符串字段应 承载整个对象;不能由 Demo 先提取 `hello` 再声称完成该输入契约。 | 位置 | 负责的工作 | | --- | --- | -| Demo / 调用方 | 读取样例、构造 C ABI 或 Operator 载体、持有缓冲区、调用 SDK、复制或展示 SDK 返回值 | -| `IBizAdapter::Unpack` | 校验外部请求、解析完整载荷、选择业务字段,转换为请求内的中性值 | -| Pipeline / Nodes | 对内部 typed ports 的数据执行算法;可解析模型生成的结构化内容,不承担外部 ABI 协议转换 | -| Adapter `Pack` / `PackTyped` | 按外部契约选择、组装和序列化结果,检查来源、错误状态和输出容量 | -| Operator bridge | 在宿主载体与同一业务 Adapter 的 C 输入/业务 Result 之间转换,保持业务语义一致 | +| Demo / 调用方 | 读取样例、构造 Operator 载体、持有缓冲区、调用 SDK、复制或展示 SDK 返回值 | +| `InputConverter::decode_fn` | 校验外部请求、解析完整载荷、选择业务字段,转换为请求内的中性值发布至 `AlgContext` | +| Pipeline / Nodes | 对内部 typed ports 的数据执行算法;可解析模型生成的结构化内容,不承担外部协议转换 | +| `OutputConverter::encode_fn` | 从 `AlgContext` 读取中性结果,按外部契约组装序列化响应并写入已租用输出池 | +| `IoBinding` | 声明业务逻辑端口与 Pipeline Blackboard Key 的映射关系,将转换器与业务编排关联 | Demo 输出里的日志、统计和展示字段可以另行组织,但不能为 SDK 补做业务字段提取、 -字段改名、响应组装或默认成功结果。C ABI 调用方直接调用 SDK 就应获得约定响应。 -Catalog 的 ingress/egress 是 Adapter 与 Pipeline 之间的内部端口,不能当成外部请求格式。 +字段改名、响应组装或默认成功结果。宿主程序直接调用 Operator SDK 就应获得约定响应。 +Catalog 的 ingress/egress 是转换器与 Pipeline 之间的内部逻辑端口,不能当成外部请求格式。 完整示例见[翻译方案](../solutions/translate.md)。 ## 1. 先确定要走哪条路径 @@ -30,17 +30,17 @@ Catalog 的 ingress/egress 是 Adapter 与 Pipeline 之间的内部端口,不 | 需求 | 修改范围与下一步 | | --- | --- | -| 完整 C ABI 业务契约不变,只调整规则、提示词、模型或连线 | 修改 Pipeline 和必要的 `.conf`,按[运行当前方案](../../tools/pipeline_studio/README.md#运行当前方案)验证;复用已有 Adapter、bridge 和 Demo | -| 完整 C ABI 业务契约不变,但已有 Node 无法完成算法 | 按[自定义 Node 入门](first_custom_node.md)实现缺失算法,再复用已有接入路径 | -| 外部载荷的字段/格式/语义改变,或需要新的平台结构 | 完成下文的契约和 Adapter 步骤,按需注册业务与 bridge;已有 C 载体和 ValueType 可以复用 | -| 修复已有 C ABI 的转换逻辑 | 修改对应 Adapter 并运行相关契约测试;仅影响该路径时,无需另建 Operator 或 Demo | +| 外部业务契约不变,只调整规则、提示词、模型或连线 | 修改 Pipeline 和必要的 `.conf`,按[运行当前方案](../../tools/pipeline_studio/README.md#运行当前方案)验证;复用已有转换器、绑定和 Demo | +| 外部业务契约不变,但已有 Node 无法完成算法 | 按[自定义 Node 入门](first_custom_node.md)实现缺失算法,再复用已有接入路径 | +| 外部载荷的字段/格式/语义改变,或需要新的平台结构 | 实现并注册对应 `InputConverter`、`OutputConverter` 与 `IoBinding`;已有载体和 ValueType 可以复用 | +| 修复已有业务的转换逻辑 | 修改对应输入/输出转换器并运行相关契约测试;仅影响该路径时,无需另建 Demo | “结构体布局相同”不等于“业务契约相同”:同一个 `const char*` 承载纯文本与承载完整 -JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代表 Adapter 已支持新协议。 +JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代表转换器已支持新协议。 -当前共享 SDK 的 Operator 初始化会审计**所有已注册 Adapter**。新增生产 Adapter -必须有匹配的 bridge,否则整个 Operator 初始化失败;`GetOperatorLastError()` 会指出业务与缺失 bridge、 -不匹配类型或槽位原因。重复初始化保留首次冲突原因。目前没有仅注册 C ABI 业务的豁免模式。已有宿主类型可以复用其 ValueType 注册,全新类型才需要增加注册。 +当前共享 SDK 的 Operator 初始化会全量审计**所有已声明业务的曝光与绑定**。新增生产业务 +必须有完整的 Operator 绑定与转换器注册,`required_transports` 统一为 `{"operator"}`; +若缺少绑定,SDK 全局初始化失败。 ## 2. 用一个现有业务看清文件关系 @@ -64,94 +64,75 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 | 环节 | 样例文件 | 新业务要落实的内容 | | --- | --- | --- | -| 本地模拟平台结构 | [C ABI 类型](../../include/platform_mock/alg_types.h)、[Operator 数据结构](../../include/platform_mock/operator_data_types.h)、[平台交互类型](../../include/platform_mock/operator_types.h) | 业务类型、输入输出字段、长度和所有权;本目录只保存当前环境的模拟约定,真实公司定义在授权内网接入 | +| 本地模拟平台结构 | [Operator 数据结构](../../include/platform_mock/operator_data_types.h)、[平台交互类型](../../include/platform_mock/operator_types.h) | 外部输入输出字段、长度和所有权;本目录只保存当前环境的模拟约定,真实公司定义在授权内网接入 | | 内部数据边界 | [业务 key](../../include/adapter/biz_blackboard_keys.h)、[业务 Result](../../include/adapter/biz_results.h) | ingress/egress typed key 与接入适配层持有的结果值;已有类型可复用 | -| Adapter | [keyword_match_adapter.cpp](../../src/adapter/biz/keyword_match_adapter.cpp) | `GetDescriptor`、`Unpack`、`PackTyped` 和 `REGISTER_BIZ_ADAPTER` | +| 输入转换器 | [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) | 声明逻辑端口映射、批次上限、`REGISTER_IO_BINDING` 与 `REGISTER_BIZ_EXPOSURE` | | Operator 类型注册 | [operator_builtin_value_types.cpp](../../src/adapter/operator/operator_builtin_value_types.cpp) | 为新宿主类型登记规范后缀、输入校验或输出分配/重置/释放 | -| Operator 业务桥接 | [keyword_match_operator_bridge.cpp](../../src/adapter/biz/keyword_match_operator_bridge.cpp) | 两端结构的字段转换、输入输出槽及 `REGISTER_OPERATOR_BIZ_BRIDGE` | | Demo 数据转换 | [keyword_match_demo.cpp](../../demo/biz/keyword_match_demo.cpp) | 读数据集、构造平台输入、复制输出字段及 `REGISTER_DEMO_BIZ` | | 构建与部署 | [接入适配层 CMake](../../src/adapter/CMakeLists.txt)、[Demo CMake](../../demo/CMakeLists.txt)、[Pipeline](../../configs/pipeline_keyword_match_rules.json)、[部署配置](../../configs/pipeline_keyword_match_rules.conf) | 登记新增 `.cpp`,编排业务端口,配置路径和输出容量 | 其中,Pipeline 的 `biz_name`(`keyword_match_v1`)、Demo 的 `--biz`(`keyword_match`) 和 Operator 槽位后缀(`keyword_in` / `keyword_out`)用途不同。 -前两者的关联由 Adapter 的 `BizDefinition` 声明;槽位由 bridge 绑定到已注册宿主类型。 +前两者的关联由 binding 的 `BizDefinition` 声明;槽位由绑定关联到已注册宿主类型。 新名字必须分别登记,不能只改 JSON 中的显示名称。 -## 3. 实现并注册 Adapter - -新增 Adapter 放在 `src/adapter/biz/_adapter.cpp`,参照上面的关键词实现完成: - -1. **声明外部契约。** 当前本地示例的 C 数据结构放在 `platform_mock/alg_types.h`, - 使用 C11 类型,明确批次上限、指针有效期和输出容量;函数入口仍在 `edgeflow/c_api.h`。 - 在 `biz_blackboard_keys.h` 复用或增加 typed key;Node 使用中性 Batch 和逻辑端口, - 不包含平台结构或业务 key 头。 -2. **填写描述符。** `GetDescriptor()` 返回业务枚举、结构名、所有权与批次约束; - `AdapterDescriptor::biz_definitions` 中的 `BizDefinition` 声明允许的 `biz_name`、Demo 名和 - ingress/egress。配置里的业务名必须与这里一致。 -3. **实现输入转换。** `Unpack` 使用 `AdapterValidationHelper` 检查批次、指针和长度, - 将输入复制到本次 `AlgContext`。关键词样例把外部请求编号保存在 `raw_request_ids`, - Batch 的 `req_id` 使用批内编号;输出阶段必须按来源映射回原编号。 -4. **实现一次结果打包。** 继承 `ResultPackingAdapter`, - 在 `PackTyped` 中检查结果完整性、按来源关联结果并映射字段。 - `COutput` 是公共 C 输出;`Result` 放在 `biz_results.h`,字符串由它自己持有。 - 字符串字段使用 `CopyResultString`:C 数组容量不足时报错,Result 则保存完整内容。 -5. **登记并编译。** 文件末尾使用 `REGISTER_BIZ_ADAPTER`,将源码加入 - `src/adapter/CMakeLists.txt` 的 `edgeflow_integration_objects`。新业务的 Operator - bridge 也要完成后再验证 SDK 初始化,不修改 `c_api_adapter.cpp` 的中央分发。 - -对于 1:1 文本类业务(如外部 JSON 协议转换或普通文本),可直接声明 `OneToOneTextAdapterSpec` 并使用 `OneToOneTextAdapter` 模板(见 [translate_adapter.cpp](../../src/adapter/biz/translate_adapter.cpp)),仅需编写单样本 `DecodeRequest` 与 `EncodeResponse` 函数,骨架自动负责 Envelope 校验、全批 copy-in、批内编号与多键发布。其 Operator 桥接可直接复用 `MakeTextCarrierBridge`。多路内部输入/输出的打包可参考 -[doc_qa_adapter.cpp](../../src/adapter/biz/doc_qa_adapter.cpp)(采用 `ReadMultiWayResults` 与 `RequestResults` 对齐组件)。平台边界检查的独立 -练习见 [Adapter 安全示例](adapter_templates/README.md),它们不注册生产业务。 - -## 4. 补齐 Operator 类型和业务桥接 - -**ValueType 说明“这块平台内存是什么类型、如何检查和管理”,bridge 说明“这个业务如何转换它”。** -按这个顺序实现: +## 3. 实现并注册转换器与绑定 + +参照关键词或实体抽取的转换器实现完成: + +1. **实现输入转换器(`src/adapter/input/`)。** + 编写 `DecodeInputFn`,使用 `AdapterValidationHelper` 检查批次、指针和长度, + 将输入复制为中性 DTO 发布到 `AlgContext`。外部请求编号保存在 `raw_request_ids`, + 内部批次使用批内编号;输出阶段按来源映射回原编号。 + 定义 `InputConverterDefinition`(`transport = "operator"`)并使用 + `REGISTER_INPUT_CONVERTER` 注册。 +2. **实现输出转换器(`src/adapter/output/`)。** + 编写 `EncodeOutputFn`,从 `AlgContext` 读取内部结果,检查结果完整性,按来源映射关联结果。 + 通过 `ExternalOutputBatchView` 将字段写入已租用的输出池结构(如 `CompanyOperator*Output`), + 使用 `slot_capacities` 严格防护缓冲区溢出。 + 定义 `OutputConverterDefinition`(`transport = "operator"`)并使用 + `REGISTER_OUTPUT_CONVERTER` 注册。 +3. **实现业务绑定与曝光声明(`src/adapter/biz/`)。** + 在 `IoBindingDefinition` 中指定 `binding_id`、`biz_name`、`transport = "operator"`、 + 绑定的 `input_converter_id` 和 `output_converter_id`,以及逻辑端口到内部 Blackboard Key 的映射。 + 使用 `REGISTER_IO_BINDING` 注册绑定。 + 使用 `REGISTER_BIZ_EXPOSURE` 声明业务生产暴露:`required_transports = {"operator"}` 与 `max_batch_size`。 +4. **登记构建。** + 将新增源码加入 `src/adapter/CMakeLists.txt` 的 `edgeflow_integration_objects`。 + +## 4. Operator 类型与输出池 + +**ValueType 说明“这块平台内存是什么类型、如何检查和管理”,输出池负责有界租约与复用。** 1. 当前环境的模拟宿主结构先在 `platform_mock/operator_data_types.h` 声明,类型实现 包含 `adapter/operator_value_type.h`,通过 `RegisterOperatorValueType` 与 `REGISTER_OPERATOR_VALUE_TYPE` 在自己的源码中登记。 输入 binding 指定规范后缀、外部类型、I/O 方向和校验函数;输出 binding 还需声明 每个字符串的默认/最大容量、metadata 上限、池载荷预算及分配、重置、释放行为。 - 同文件的 `MakeTypedInputBinding` / `MakePooledOutputBinding` 是现有类型的实现参考。 - 公司内部公共头的接入遵循 [平台模拟定义的迁移边界](../../include/platform_mock/README.md)。 - 同一外层类型的不同嵌套布局可以注册命名分配方案,配置通过 `allocator` 和 `params` - 选择;用 `MakeOutputParameterParser` 注册自己普通参数结构的字符串解析函数, - 无需继承参数基类或实现 `ToJson()`。配置只在最外层创建阶段读取,分配函数仅创建 - 一份完整结构,不管理 25 的池深。见 - [多输出与嵌套载荷分配](operator_output_allocation.md)。 -2. 新建 `src/adapter/biz/_operator_bridge.cpp`。单输入/单输出时,沿用 - `MakeSingleSlotBizBridge`,填写与 Adapter 一致的业务类型和结果类型,指定 - 已注册的输入输出后缀;多输入时参考 - [ocr_doc_qa_operator_bridge.cpp](../../src/adapter/biz/ocr_doc_qa_operator_bridge.cpp)。 -3. 实现 `convert_sample_input`:从输入槽读取宿主结构,使用 - `ProcessLocalShadowStorage` 复制字符串、保存临时输入结构。在输出槽位提供 - `convert_output`(单输出 helper 会绑定到 `output_slots.front().convert_output`): - 把业务 Result 复制进已租用的输出池,容量只取自 `ResolvedOutputPoolSpec`,不在 bridge 内重新读取原始 JSON 或设置默认容量。 -4. 包含 `adapter/operator_biz_bridge.h`,使用无参注册函数调用 - `RegisterOperatorBizBridge(desc)`;输出文本使用 `CopyToOperatorString`。 - 用 `REGISTER_OPERATOR_BIZ_BRIDGE` 登记注册函数,并将新增源码加入 - `src/adapter/CMakeLists.txt`。宿主传入的命名 I/O Key 后缀必须与注册后缀精确一致。 +2. 同一外层类型的不同嵌套布局可以注册命名分配方案,配置通过 `allocator` 和 `params` + 选择;用 `MakeOutputParameterParser` 注册自己普通参数结构的字符串解析函数。 + 配置只在最外层创建阶段读取,分配实现仅创建一份完整结构,不管理池深。 `CompanyString` 用于文本,二进制使用 `CompanyBuffer`。输入借用指针不跨调用保存; 输出提取时复制所需数据,具体生命周期按 [Operator 接口](../../include/edgeflow/operator/interface.h)执行。 上述转换都留在接入适配层,Node、Model 和 Backend 无需识别宿主结构。 -## 统一 Demo 接入 +## 5. 统一 Demo 接入 已有契约的新方案直接沿用对应 Demo 和数据集格式,只准备 Pipeline 与指向它的 `.conf`。 -这里的数据转换仅指[载体构造和结果展示](#输入输出以-c-abi-为边界),外部协议的解包、 -字段选择与响应组装仍在 Adapter;不得把原始业务请求预先拆成内部节点输入。 +这里的数据转换仅指载体构造和结果展示,外部协议的解包、 +字段选择与响应组装仍在转换器;不得把原始业务请求预先拆成内部节点输入。 新增外部结构需要统一 Demo 时,按 `keyword_match_demo.cpp` 完成以下步骤: 1. 新建 `demo/biz/_demo.cpp`,从数据集读入样本,为每条样本构造宿主输入结构, 保持字符串及数组在同步处理期间有效。 -2. 使用 `RunOperatorWithExtractor`,传入 bridge 声明的槽位后缀; +2. 使用 `RunOperatorWithExtractor`,传入声明的槽位后缀; 在 extractor 中把输出复制到本地结果值,再交给 `ResultWriter` 输出逐条记录。 同时复制真实 `status_code`,写入样本的 `status`,不能固定填零。Process 返回成功表示 调用完成,业务是否逐条成功还需检查 `results.jsonl` 和 `summary.json`。 - 执行、参数解析和输出池管理继续复用运行器。 3. 用 `REGISTER_DEMO_BIZ(name, title, run_function, biz_type)` 注册,`name` 与 `BizDefinition` 的 Demo 名一致,业务类型显式给出且非 UNKNOWN;将源码加入 `demo/CMakeLists.txt`。无需在 `demo/main.cpp` 增加业务分支。 @@ -164,7 +145,7 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 `model_path`;同时核对 `data.model_paths` 覆盖与输出容量。Profile 不会自动指向新方案, 详细命令见[运行当前方案](../../tools/pipeline_studio/README.md#运行当前方案)。 -## 输出容量 +## 6. 输出容量与生命周期 宿主输入是借用视图,底层字符串、数组和结构体必须保持有效直到 `Process` 返回。 输出 `shared_ptr` 持有的是当前 handle 的池租约,不延长 handle 的生命期。 @@ -177,15 +158,12 @@ JSON 请求是不同的输入约定。已有 Nodes 能完成算法,也不代 旧输出。参考 [Demo 的输出复制与释放](../../demo/biz/ocr_doc_qa_demo.cpp) 和 [公开 Operator 契约](../../include/edgeflow/operator/interface.h)。 -Operator 的输出路径是 `Pipeline → 可变长业务 Result → 已租用输出池`。 +Operator 的输出路径是 `Pipeline → 内部中性值 → OutputConverter → 已租用输出池`。 Result 与请求 Context 均不跨 Process 保存。`.conf` 的 `data.outputs` 按逻辑 -槽位分别指定类型、`allocator`、`params` 和容量。字符串不受中间 C 输出数组大小限制; +槽位分别指定类型、`allocator`、`params` 和容量。 超过输出池容量时返回 `-4`,尚未发布的输出租约全部回滚。 -公共 C ABI 继续使用已发布的固定数组大小。需要大结果时选择 Operator,或通过新的 -外部 ABI 版本扩展;不改变旧结构布局。 - -## 最小验证 +## 7. 最小验证 完成源码登记后重新构建,再检查新业务是否进入 Catalog: @@ -194,7 +172,7 @@ cmake --build build --target alg_sdk alg_pipeline_tool alg_demo -j 4 ./build/alg_pipeline_tool catalog ``` -确认 Catalog 中出现新 `biz_name`,ingress/egress 类型与 Adapter 一致;随后对**本次新增或 +确认 Catalog 中出现新 `biz_name`,ingress/egress 类型与定义一致;随后对**本次新增或 修改的 Pipeline** 执行 `validate`、`plan`,运行对应 Demo 并核对请求 ID、状态及业务字段。 第 2 节的关键词命令是可运行参照,实际验证时替换为新业务、配置和数据集。 有意使用测试模型时按[工具选择](../../tools/pipeline_studio/README.md#校验工具选择) @@ -204,13 +182,10 @@ cmake --build build --target alg_sdk alg_pipeline_tool alg_demo -j 4 | 验证范围 | 必须观察到的行为 | 参考测试 | | --- | --- | --- | -| C ABI / Adapter | 非法指针和长度被拒绝;结果乱序仍按来源返回,重复/缺失来源与失败结果被拒绝;两种输出表示一致 | [Adapter 契约测试](../../tests/contract/abi/test_adapter_contract_security.cpp)、[C ABI 测试](../../tests/contract/abi/test_c_abi_safety.cpp) | -| Operator | 初始化接受完整注册;超过旧 C 数组但在池容量内时输出完整,超池容量时无部分发布且后续请求可复用租约 | [bridge 测试](../../tests/unit/operator/test_operator_biz_bridge_registry.cpp)、[Operator 集成测试](../../tests/integration/operator/test_operator_api.cpp) | +| 转换器与契约安全 | 非法指针和长度被拒绝;结果乱序仍按来源返回,重复/缺失来源与失败结果被拒绝;输出容量越界严格拦截 | [Adapter 契约测试](../../tests/contract/abi/test_adapter_contract_security.cpp)、[Operator 安全测试](../../tests/contract/abi/test_operator_safety.cpp) | +| Operator SDK | 初始化接受完整注册;在池容量内时输出完整,超池容量时无部分发布且后续请求可复用租约 | [Operator 基础测试](../../tests/integration/operator/test_operator_api.cpp)、[公开 SDK 消费者测试](../../tests/contract/abi/test_cpp_operator_sdk.cpp) | | Pipeline / Demo | 新业务通过校验和计划,样例结果及错误路径符合预期 | [Catalog/Validator 测试](../../tests/integration/pipeline/test_pipeline_catalog_validator.cpp)、[Demo 测试](../../tests/integration/demo/test_demo_runner.cpp) | -业务 I/O 契约新增或改变时,必须用原始业务请求直接调用 `Alg_Process`,检查完整响应; -已有契约测试可以复用。统一 Demo 走 Operator,其成功不替代 C ABI 边界测试。 -仅修改 C ABI 路径时,无需另建 Demo。 交付前执行 `./scripts/run_all_tests.sh`。真实模型效果与目标平台验收按 [效果验收指南](../VERIFIABLE_SELECTION.md)另行记录;涉及公司内部 SDK 时遵循 [RFC-0029](../rfcs/0029-external-readiness-and-intranet-sdk-migration.md),当前外部工作区只准备中立接口。 diff --git a/doc/dev_guide/source_layout.md b/doc/dev_guide/source_layout.md index d2cf289a..131a0838 100644 --- a/doc/dev_guide/source_layout.md +++ b/doc/dev_guide/source_layout.md @@ -14,7 +14,7 @@ | 使用者 | 位置与构建目标 | 约定 | | --- | --- | --- | -| SDK 调用方 | `include/edgeflow/`;`edgeflow_public_headers` | C ABI、Operator、日志及生成的版本头;只有明确列举的调用头向 SDK 消费方传播 | +| SDK 调用方 | `include/edgeflow/`;`edgeflow_public_headers` | Operator、日志及生成的版本头;只有明确列举的调用头向 SDK 消费方传播 | | 源码扩展开发者 | `include/adapter/`、`include/core/`、`include/nodes/`、`include/engine/`、`include/contracts/`;`edgeflow_extension_headers` | Adapter、Node、Model、Backend 的源码接口与共享契约,需要随框架重新编译,不承诺内部 C++ 动态 ABI | | 模块实现与仓库测试 | `src/` 中与 `.cpp` 相邻;`edgeflow_internal_headers` | 运行时装配、配置解析、注册表内部及输出池等实现 | @@ -32,41 +32,36 @@ ```text include/edgeflow/ SDK 调用接口 - c_api.h / c_api.hpp export.h / log.h operator/interface.h - operator/types.h 兼容转发 + operator/types.h 平台交互类型门面 include/platform_mock/ 本地平台公共定义模拟 - alg_types.h / error_codes.h + error_codes.h operator_data_types.h / operator_types.h include/adapter/ 源码扩展契约与辅助接口 - biz_adapter_interface.h - biz_adapter_registry.h - operator_biz_bridge.h - operator_io_contracts.h + io_binding.h + io_converter.h + io_binding_registry.h + io_converter_registry.h operator_value_type.h src/adapter/ - c_api_adapter.cpp shared_algorithm_runtime.cpp/.h - deployment_model_resolver.cpp/.h - biz_adapter_registry.cpp - biz/ 每个业务的两个转换文件相邻 - doc_qa_adapter.cpp - doc_qa_operator_bridge.cpp + deployment_io_config.cpp/.h + io_binding_resolver.cpp/.h + input/ 各业务输入转换器 + output/ 各业务输出转换器 + biz/ 各业务 I/O 绑定声明 operator/ Operator 通用机制 operator_config_resolver.cpp/.h - json_output_config_reader.cpp/.h - operator_biz_bridge_registry.cpp/.h - operator_output_pool.cpp/.h + operator_process_binding.cpp/.h + operator_adapter.cpp ``` -Bridge 作者包含 `adapter/operator_biz_bridge.h`,使用 `MakeSingleSlotBizBridge`、 -`RegisterOperatorBizBridge`、`CopyToOperatorString` 和 `REGISTER_OPERATOR_BIZ_BRIDGE`。 -注册函数不接收注册表实例;描述符与转换代码无需包含内部注册表或输出池头。 -新宿主值类型与命名输出分配方案通过 `adapter/operator_value_type.h` 登记;实现只管理 +转换器作者包含 `adapter/io_converter.h` 与 `adapter/converter_authoring.h`,实现 `InputConverter` 与 +`OutputConverter` 纯虚类,并通过 `REGISTER_INPUT_CONVERTER` 和 `REGISTER_OUTPUT_CONVERTER` 注册。 +各业务接入绑定在 `src/adapter/biz/` 中声明 `IoBindingDefinition`,通过 `REGISTER_IO_BINDING` 注册。 +宿主值类型与命名输出分配方案通过 `adapter/operator_value_type.h` 登记;实现只管理 单份结构及嵌套存储,队列、租约和初始化审计归通用机制所有。 -配置读取接口 `adapter/operator_output_config.h` 使用固定枚举及字符串,不暴露 JSON; -结构体作者用 `MakeOutputParameterParser` 登记普通参数结构的解析,无需编写读取器。 完整步骤见[业务接入](business_onboarding.md)。 ## 标识符与定义 @@ -96,24 +91,18 @@ Catalog JSON 为兼容现有消费者,继续在两种声明中输出 `key`, | 规范入口 | 说明 | | --- | --- | -| `edgeflow/c_api.h` | 纯 C ABI 导出头 | -| `edgeflow/c_api.hpp` | C++ 异常屏障与辅助包装 | | `edgeflow/export.h` | 符号可见性宏 | | `edgeflow/log.h` | 统一日志入口 | | `edgeflow/version.h` | 版本头(由 CMake 生成) | | `edgeflow/operator/interface.h` | Operator 纯 C 接口及函数表 | | `edgeflow/operator/types.h` | Operator 平台交互类型门面(转发至 platform_mock) | -`Alg_*`、`Company*`、公共宏、C/C++ 公开函数签名、结构布局及 `libcompany_alg_sdk` -名称保持原样;这些名称属于既有调用契约。内部扩展应更新 `BizName()`、`pipelines`、 -`abi_version` 等旧成员。原 `NodeFactory` 兼容别名已移除,代码统一使用 `NodeRegistry`。 -业务 bridge 转为无参注册函数,并使用上述扩展入口。 +`Company*`、公共宏、C++ Operator 公开函数签名、结构布局及 `libcompany_alg_sdk` +名称保持原样;这些名称属于既有调用契约。内部扩展统一使用 `NodeRegistry`。 -`edgeflow/c_api.h` 的参数类型来自 `platform_mock/alg_types.h`,错误码来自 -`platform_mock/error_codes.h`;`edgeflow/operator/types.h` 转发到 -`platform_mock/operator_data_types.h`,Operator 平台交互类型集中在 -`platform_mock/operator_types.h`。现有兼容范围是本仓库的调用约定,真实公司公共头 -需要在授权内网单独核对和接入。 +`edgeflow/operator/types.h` 转发到 `platform_mock/operator_data_types.h`, +Operator 平台交互类型集中在 `platform_mock/operator_types.h`,错误码来自 `platform_mock/error_codes.h`。 +现有兼容范围是本仓库的调用约定,真实公司公共头需要在授权内网单独核对和接入。 示例配置与 Profile 的旧新名称见[配置迁移表](../../configs/README.md)。Pipeline JSON 业务 ID、节点类型、模型和 Backend ID、端口绑定以及算法参数均保持原样。 diff --git a/doc/developer_guide.md b/doc/developer_guide.md index 3f94daf6..1b36dcab 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -22,21 +22,21 @@ Backend;出现调度、模型语义或硬件能力缺口时,再查阅相应 | 架构层 | 新增什么? | 核心修改文件 | 关键宏 / 核心类 | | :--- | :--- | :--- | :--- | -| **接入适配层(Integration)** | 新增业务枚举、输入/输出纯 C 结构体与专属适配器 | `include/edgeflow/c_api.h`
`src/adapter/biz/_adapter.cpp` | `CompanyAlgBizType`
`IBizAdapter`
`REGISTER_BIZ_ADAPTER` | +| **接入适配层(Integration)** | 新增输入/输出结构、转换器与业务绑定 | `include/platform_mock/operator_data_types.h`
`src/adapter/input/_input.cpp`
`src/adapter/output/_output.cpp`
`src/adapter/biz/_bindings.cpp` | `InputConverterDefinition`
`OutputConverterDefinition`
`IoBindingDefinition`
`REGISTER_INPUT_CONVERTER`
`REGISTER_OUTPUT_CONVERTER`
`REGISTER_IO_BINDING` | | **流程编排层(Orchestration)** | 扩展动态黑板、会话模型管理与全局资源 | `include/core/alg_context.h`
`include/core/session_context.h` | `AlgContext::Read/Publish`
`SessionResourceKey` | | **能力节点层(Capability Nodes)** | 新增通用操作或可跨方案复用的领域算法 | `src/common_nodes/*.cpp`
`src/custom_nodes/*.cpp`
`include/nodes/*.h` | `NodeBase`
`REGISTER_NODE_WITH_DEFINITION(NodeName, def)` | | **模型执行层(Model Execution)** | 新增模型语义或接入新推理后端 | `include/engine/model_interface.h`
`include/engine/backend_interface.h`
`src/engine/models/`
`src/engine/backends/` | `REGISTER_MODEL_WITH_DEFINITION`
`REGISTER_BACKEND_WITH_DEFINITION`
`ModelRuntimeFactory`
`FixedBatchExecutor` | --- -## 1. 接入适配层:如何新增一个业务的 C ABI 接口与专属 Adapter +## 1. 接入适配层:如何新增一个业务的 Operator 转换器与绑定 -> ⚠️ **平台治理红线**:普通业务接入严禁修改中心分发文件 `src/adapter/c_api_adapter.cpp`,必须编写业务专属 Adapter 类并注册。 +> ⚠️ **平台治理红线**:业务接入采用独立的 InputConverter、OutputConverter 与 IoBinding 注册,严禁在 central dispatch 开关中侵入硬编码。 -业务需求的输入输出以完整 C ABI 请求/响应为准,由 Adapter 解包、转换和组装。 -即使复用同一个 C 结构,字符串内部协议变化仍可能需要 Adapter 实现;不能用 Demo +业务需求的输入输出以完整 Operator 请求/响应为准,由输入/输出转换器解包、转换和组装。 +即使复用同一个 DTO 结构,字符串内部协议变化仍可能需要转换器实现;不能用 Demo 预处理或后处理补足 SDK 契约。职责划分与复用判断见 -[输入输出边界](dev_guide/business_onboarding.md#输入输出以-c-abi-为边界)。 +[输入输出边界](dev_guide/business_onboarding.md)。 ### Operator 镜像结构与输出池扩展指南 @@ -63,9 +63,9 @@ Create 期固定池分配,Process 只向空输出槽位提交池化 shared_ptr 持有池状态的 weak lifetime token,Destroy 后不得访问输出数据。任何需要修改 Blackboard、Node、Model 或 Backend 才能识别 Operator 结构的方案均违反分层要求。 -目标交付共享库为 `company_alg_sdk`,产品 VERSION 为 10.0.0, -SOVERSION/C ABI major 为 6。 -其正式动态符号面固定为 6 个 `Alg_*`、3 个 `AlgBase_*` 和 3 个 Operator 入口; +目标交付共享库为 `company_alg_sdk`,产品 VERSION 为 11.0.0, +SOVERSION/ABI major 为 7。 +其正式动态符号面固定为 3 个 `AlgBase_*` 和 3 个 Operator 入口; 仓库内 Node、Registry、Model、Backend 和第三方运行时是隐藏实现,不得被外部扩展直接链接。 Operator v4 的 Create 和配置预检都使用部署根 `model_path` 加相对 `cfg_file_name`。每份 `.conf` 的根对象只能包含 `data`,`data` 只接受 @@ -90,18 +90,17 @@ Biz egress 描述 Adapter 消费的内部端口。普通一对一出口仍要求 CrossRerank 的排名数组和 Compliance 的首项选择使用 `N:1 / aggregate`。 预检检查声明兼容性,打包阶段仍检查实际请求来源、排名及输出容量。 -1. 当前环境的模拟平台枚举和 C 数据结构放在 `platform_mock/alg_types.h`,只使用 - C11 类型并明确所有权;函数入口保留在 `edgeflow/c_api.h`。公开结构体变更必须先有 RFC。 -2. 在 `src/adapter/biz/` 实现无请求状态的 `IBizAdapter`,用 - `AdapterValidationHelper` 完成批次、指针、长度和输出容量校验。 -3. `AdapterDescriptor::biz_definitions` 使用完整 `BizDefinition` 声明合法 `biz_name` 及 - ingress/egress typed ports;通过 `REGISTER_BIZ_ADAPTER` 注册,不修改中心派发。 -4. `Unpack`/`Pack` 使用 `core/common_contracts.h` 中的中性值类型,并在 +1. 当前环境的模拟平台枚举和数据结构放在 `platform_mock/operator_data_types.h` 与 + `operator_types.h`,函数入口统一为 `edgeflow/operator/interface.h`。公开结构体变更必须先有 RFC。 +2. 在 `src/adapter/input/` 实现无状态的 `InputConverter`,用 + `AdapterValidationHelper` 完成批次、指针和长度校验,发布中性数据至 `AlgContext`。 +3. 在 `src/adapter/output/` 实现 `OutputConverter`,完成输出结构租约组装与容量检查。 +4. 在 `src/adapter/biz/` 实现 `IoBinding` 绑定,声明业务逻辑端口到 Blackboard 的映射, + 并通过 `REGISTER_BIZ_EXPOSURE` 声明 `required_transports = {"operator"}`。 +5. 解码与编码使用 `core/common_contracts.h` 中的中性值类型,并在 `adapter/biz_blackboard_keys.h` 集中声明业务 ingress/egress `BlackboardKey`; Core、Node 和 Engine 不得包含该业务 key 头。 -5. 以 [`entity_extract_adapter.cpp`](../src/adapter/biz/entity_extract_adapter.cpp) 和 - [`cross_rerank_adapter.cpp`](../src/adapter/biz/cross_rerank_adapter.cpp) 为当前模板, - 并扩展 Adapter/C ABI/Operator 对应契约测试。 +6. 扩展对应的 Operator 契约测试与安全测试。 --- diff --git a/doc/rfcs/0060-cpp-operator-only.md b/doc/rfcs/0060-cpp-operator-only.md new file mode 100644 index 00000000..76b09345 --- /dev/null +++ b/doc/rfcs/0060-cpp-operator-only.md @@ -0,0 +1,572 @@ +# RFC-0060:删除 C ABI,仅保留 C++ Operator API + +- **RFC 编号**:`0060-cpp-operator-only`(建议编号;基线索引最高为 0059,正式入库前检查编号占用) +- **创建日期**:2026-09-17 +- **文档状态**:Completed +- **仓库**:`chamsechan/LLM-EdgeFlow` +- **审查基线**:默认分支 `main`,提交 `2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9` +- **基线提交日期**:2026-09-16 08:26:57 UTC +- **建议实施分支**:`refactor/cpp-operator-only`(本次未创建) +- **建议入库路径**:`doc/rfcs/0060-cpp-operator-only.md` +- **目标版本**:建议 `v11.0.0`,共享库 ABI major 建议由 6 升为 7;版本号为本 RFC 提案,不代表已经发布 +- **负责人**:项目维护者指定 +- **关联决策**:继承 RFC-0059 的输入/输出转换独立化;取代其中“两种宿主算法入口并存”的部分。保留 RFC-0049、RFC-0050 的 Operator 输出分配与配置边界,以及 RFC-0014 的独立日志 API。 + +> **本次唯一方向:彻底删除旧 C ABI 算法调用链,只保留现有 C++ Operator API。** +> 不保留兼容头、转发函数、旧符号、旧绑定别名或开关;不把 Operator 改造成另一套新 API;不重新设计帧深度、输出池、输入/输出转换器或 Pipeline。 + +## 1. 问题与范围 + +### 1.1 需求与完成后的产品边界 + +宿主程序今后仅通过 `llm_edgeflow::operator_api` 获取 Operator 函数表,并使用其中的 `Init / Create / Process / Control / Destroy / Deinit` 调用算法。旧 `Alg_*` 调用方、旧 C ABI 类型和旧 `*.cabi.*` 部署绑定不再受支持,必须修改源码与配置后重新构建。 + +“只保留一套接口”限定为**宿主算法调用接口**。这不等于删除公共日志、Pipeline 编排工具、内部 Node/Model/Backend 作者接口,也不等于删除所有 C 风格结构体或所有 `extern "C"` 声明。 + +本 RFC 不提供过渡兼容版本。实施分支可以按阶段保留尚未删除的原代码以保证迁移可验证,但不得新增兼容设施,最终交付必须完成整体切换。 + +### 1.2 当前代码事实 + +以下事实来自固定提交的源码,而不是把历史方案当作当前实现。源码依据见文末 `[S01]`—`[S20]`。 + +| 位置 | 基线事实 | 对实施的影响 | +| --- | --- | --- | +| `include/edgeflow/c_api.h` | 声明六个 `Alg_*` C ABI 函数,`Alg_Process` 使用指针数组及数量参数 | 整个旧算法入口应删除 | +| `include/edgeflow/c_api.hpp` | 三参数 `Alg_Process` 是 `std::vector` 到旧 C ABI 的便捷包装 | 它不是 Operator API,必须一并删除 | +| `include/edgeflow/operator/interface.h` | 已有函数表入口、线程局部错误查询、配置绑定预检 | 直接保留,不另造门面 | +| `src/adapter/operator/operator_adapter.cpp` | 已自行完成命名槽位校验、Decode、租用输出、Pipeline 执行、Encode 与发布 | Operator 不需要通过旧 C ABI 才能工作 | +| `src/adapter/shared_algorithm_runtime.*` | Operator 使用其初始化、已验证计划构建和 Control;同时还混有 C ABI 专用工厂与指针数组执行函数 | 保留运行时,仅删旧路径及头文件依赖 | +| `include/adapter/io_converter.h` | 同时存在指针数组访问与 Operator 具名槽位访问 | 需要删 C ABI 分支,不能仅删除外层入口 | +| `src/adapter/input/`、`output/`、`biz/` | 当前实现按输入、输出、绑定拆分;部分文件同时注册两种 transport | 按函数和注册项清理,不能整目录删除 | +| 关键词、翻译业务的绑定声明 | `required_transports` 为 `{"cabi", "operator"}` | 全量审计也必须收敛,否则删除注册后初始化会失败 | +| Demo 主入口与关键词 Demo | 已使用 Operator;公共 runner 仍包含 `edgeflow/c_api.h` | 主要清理残留依赖并回归,不重写 Demo 框架 | +| 构建与符号检查 | 当前产品版本 10.0.0、ABI major 6;共享库允许导出 12 个符号 | 旧六个算法符号与版本约束必须同步处理 | +| 测试链接方式 | 多数测试链接内部运行时;独立 C11 ABI 测试链接公开 SDK | 删除 C11 测试后必须保留“公开共享库可被消费者使用”的验证能力 | +| `AGENTS.md`、业务接入指南 | 仍有 C ABI 为中心及旧 `IBizAdapter`/业务 bridge 描述 | 修改现行指南,不能按这些旧描述重新引入已退出的架构 | + +**两个容易混淆的三参数函数:** + +```cpp +// 删除:旧 C ABI 的 C++ 便捷包装。 +Alg_Process(void* hndl, + const std::vector& inputs, + std::vector& outputs); + +// 保留:现有 OperatorFunc::Process 的函数指针类型,不修改参数。 +int (*Process)(void* handle, + const NamedIoBatch& inputs, + NamedIoBatch& outputs) noexcept; +``` + +### 1.3 范围与非目标 + +| 纳入本次 | 不纳入本次 | +| --- | --- | +| C ABI 入口、包装、专用类型、专用运行路径、注册项及配置消费者清理 | 重设计 OperatorFunc、CreateParam、NamedIo 或平台结构体 | +| 共用代码解除对 C API 头的依赖 | 新建另一套 Runtime、统一传输框架、反射系统或业务 bridge | +| 迁移业务、安全、并发、Control 与工具测试 | 改变 Pipeline、Node、Model、Backend 的算法语义 | +| 公开头可见性、动态导出、共享库版本与测试清单同步 | 为删接口而移除 C 编译器、改动第三方推理引擎 | +| 当前文档、Agent 指引、脚手架及 CI 引用清理 | 为追求命名整齐而大规模改目录或重命名仍有用的内部类 | +| 明确拒绝旧配置与旧注册 | 自动转换旧绑定、保留旧库名版本别名、提供运行时兼容开关 | + +## 2. 决策与权衡 + +### 2.1 保留现有 Operator 公共契约 + +以下现有入口和能力保持原有职责与行为:[S02][S03] + +| 保留项 | 要求 | +| --- | --- | +| `Get_LLM_EDGEFLOW_OperatorTable()` | 仍返回现有 `OperatorFunc`;不改为全新对象式接口 | +| `GetOperatorLastError()` | 保留现有线程局部诊断查询;不借此重写错误体系 | +| `ValidateOperatorConfigBinding(...)` | 继续使用与创建路径一致的 Operator 配置解析规则 | +| `OperatorFunc` 六个函数指针 | 名称、顺序、参数、`noexcept` 与职责不因本次删接口而改变 | +| `CreateParam` | 保留配置根、相对配置路径、设备、计算平台和 `max_frame_depth` | +| `NamedIo` / `NamedIoBatch` | 保留当前命名槽位和 `shared_ptr` 载体 | +| `MakeBorrowedOperatorInput` | 继续表达同步调用期间的只读借用,不扩大生命周期承诺 | +| `ControlCommand` 与参数结构 | 保留类型化命令及 `kJson + ControlJsonParam` | +| Operator 数据结构与 ValueType 注册 | 保留 `CompanyString`、`CompanyBuffer`、`CompanyAny`、图像及各业务 Operator 结构 | + +`include/edgeflow/operator/types.h` 虽然含有“Compatibility entrypoint”注释,但它是当前 Operator 公共头链的一部分。**保留该文件并纠正注释,不按关键词将其误删。** + +Operator 公共入口之外,`edgeflow/log.h`、`edgeflow/export.h`、生成的 `edgeflow/version.h` 和仍被使用的 `platform_mock/error_codes.h` 保留。公共头应直接包含自身需要的声明,不再借 `c_api.h` 间接取得错误码或版本宏。 + +### 2.2 目标调用链:沿用已有实现,不增加中间层 + +```text +宿主程序 + └─ Get_LLM_EDGEFLOW_OperatorTable() + ├─ Init / Deinit + ├─ Create + │ └─ OperatorConfigResolver + │ └─ 已验证的 IoPlan + RuntimeOptions + │ └─ SharedAlgorithmRuntime::CreateFromIoPlan + ├─ Process + │ ├─ 验证 NamedIo 输入与输出请求 + │ ├─ input converter:外部请求 → 请求内的中性数据 + │ ├─ 使用现有机制租用输出块 + │ ├─ Pipeline / Nodes / Model / Backend + │ ├─ output converter:内部结果 → 已租用的输出结构 + │ └─ 完整成功后发布输出;失败归还本次租约 + ├─ Control → 现有控制解析与运行时 Control + └─ Destroy → 按现有契约回收句柄与池 +``` + +`SharedAlgorithmRuntime` 的名字无需因为只剩一个宿主入口而修改。保留现有文件、类和组合层归属,减少无意义的改名、日志变化与包含路径变更。[S04][S05] + +该类按下表处理: + +| 成员 / 依赖 | 动作 | +| --- | --- | +| `#include "edgeflow/c_api.h"` | 删除;在实际使用处直接包含仍需要的错误码等头 | +| `GlobalInit` / `GlobalDeinit` | 保留;全局注册冲突与绑定审计不能删除 | +| `CreateFromIoPlan` | 保留;继续消费已验证计划,不绕过验证器 | +| `ExecuteControl` | 保留;Operator 仍调用 | +| `GetPipeline` / `GetIoPlan` 等有效访问器 | 保留实际使用的成员 | +| `CreateFromConfigFile` | 基线中固定使用 `"cabi"`;迁完调用方后删除 | +| `CreateFromPipelineJson` | 基线中同样固定使用 `"cabi"`;迁完调用方后删除 | +| 指针数组版 `ExecuteBatch` | C ABI 专用执行路径,迁完调用方后删除 | + +这里删除的是 **`SharedAlgorithmRuntime` 的上述 C ABI 专用方法**,不是对整个仓库按 `CreateFromPipelineJson` 或 `ExecuteBatch` 名称批量删除。`IoBindingResolver` 中仍被工具或测试使用的计划解析能力,应按实际调用关系保留,并采用唯一的 Operator 约束。 + +不把原 C ABI `ExecuteBatch` 再包装成“通用执行器”保留下来;当前 Operator 已有完整执行链,本次没有新增第二条执行链的必要。 + +### 2.3 注册与配置只支持 Operator,但不顺带重构全部元数据 + +采用**保留现有描述模型、移除双路径实现**的方案:[S06][S07][S08][S09] + +1. 删除生产代码中所有 C ABI 输入/输出转换器、`*.cabi.*` 接入绑定及其注册。 +2. 各业务 `BizExposureDefinition.required_transports` 改为 `{"operator"}`,保留业务名、批次约束和其他有效信息。 +3. `IoConverterRegistry` 的输入、输出注册,以及 `IoBindingRegistry` 的绑定注册只接受 `transport == "operator"`;空值、`"cabi"` 和其他值均拒绝。 +4. 曝光声明的 `required_transports` 只允许唯一值 `operator`。不得仅改业务初始化列表,却继续接受自定义 C ABI 注册。 +5. 配置与解析器的 transport 参数若继续存在,只允许 `operator`。校验应发生在执行或分配之前,不让错误 transport 跳过输出配置、槽位或预算检查。 +6. 删除只服务于 C ABI 的条件分支;原本仅在 `transport == "operator"` 时执行的必需校验,在完成入口约束后成为正常必经步骤。 + +**为什么不同时删除所有 `transport` 字段?** + +这些字段已经贯穿 Definition、解析接口、Catalog 和测试。将取值收敛为唯一值、删掉另一条实现,就足以兑现单入口要求。此时全面删除字段及改写工具数据格式属于另一项作者接口/元数据重构,不是删除 C ABI 的必要前置。保留一个只接受 `operator` 的描述字段,不表示保留 C ABI 兼容性,也不为未来预建多协议抽象。 + +其他约束不变:必须显式填写 `io_binding`;仍使用现有 Operator 绑定 ID;仍校验 schema、方向、端口类型、来源映射、槽位完整性、ValueType 与分配能力。禁止通过取消全量审计来“修复”删除 C ABI 后的初始化错误。 + +### 2.4 收敛转换器载体,保留方向独立性 + +`include/adapter/io_converter.h` 同时承载两种外部视图,必须清理内部遗留,而不是让废弃代码只变得不可达。[S06] + +| 内容 | 目标处理 | +| --- | --- | +| 输入视图的 C ABI 指针数组 `items` | 删除 | +| 输出视图的 C ABI 指针数组 `items` 与其专用 `capacity` | 删除;注意不能误删 Operator 字符串/槽位容量 | +| 两个 `GetCAbi` | 删除 | +| 输入 `At(index, slot_name = "")` 中空槽名退回 C ABI 的访问方式 | 删除该混合入口;有效调用方直接使用明确的 `GetSlot` | +| 旧 `Company*InputStruct` / `Company*OutputStruct` 的 `ExternalTypeTraits` | 删除;以 `platform_mock/alg_types.h` 的实际声明集合为准 | +| `count`、`type_id`、槽位类型信息、`GetSlot` | 保留仍被 Operator 使用的内容 | +| `leased_slots`、`slot_capacities`、`pool_specs` | 保留;属于 Operator 输出而非旧 C ABI | +| Operator 结构的 `ExternalTypeTraits` | 保留,不能因名字带 `Company` 而删除 | +| 输入/输出 Definition、端口映射、Decode/Encode 回调 | 沿用当前机制,不合并成一个业务 Adapter 类 | + +转换器继续保持:输入不引用输出转换器,输出不回读外部输入指针;请求数据在输入边界复制为中性值;输出依靠已声明的内部值与请求来源信息组装响应。 + +### 2.5 混合实现文件按函数和注册项删除,不整文件误删 + +当前源码清单列有以下转换器与业务声明文件。[S10] 实施者必须逐个核对其中注册的 ID、回调和共享 helper,不能根据文件名猜测文件全属 C ABI。 + +| 类别 | 必查文件(均相对 `src/adapter/`) | 清理方式 | +| --- | --- | --- | +| 输入 | `input/text_input.cpp`、`translate_json_input.cpp`、`doc_query_input.cpp`、`rerank_input.cpp`、`audit_input.cpp`、`audio_input.cpp`、`image_query_input.cpp`(后六个同属 `input/`) | 删除 C ABI Decode、工厂、注册及专用结构读取;保留 Operator 路径与共用语义解析 | +| 输出 | `output/structured_document_output.cpp`、`translation_json_output.cpp`、`keyword_result_output.cpp`、`doc_answer_output.cpp`、`rerank_result_output.cpp`、`audit_result_output.cpp`、`audio_result_output.cpp`、`invoice_result_output.cpp`(后七个同属 `output/`) | 删除固定 C 输出结构写入和 C ABI 注册;保留响应组装、来源检查、Operator 容量与池写入 | +| 业务声明 | `biz/translate_bindings.cpp`、`entity_extract_bindings.cpp`、`keyword_match_bindings.cpp`、`doc_qa_bindings.cpp`、`cross_rerank_bindings.cpp`、`compliance_audit_bindings.cpp`、`audio_asr_intent_bindings.cpp`、`ocr_doc_qa_bindings.cpp`(后七个同属 `biz/`) | 删除 C ABI Binding 工厂及注册;曝光仅要求 Operator;保留内部业务定义 | + +例如,`text_input.cpp` 现有 `DecodeCAbiTextHelper`、C ABI 文本/关键词 Decode 和 C ABI 工厂可删除,但 `DecodeOperatorEntityInput`、`DecodeOperatorKeywordInput` 及其注册必须保留。[S07] + +`AdapterValidationHelper` 等 helper 同理:移除失去调用方的 C 指针数组批次预检;保留 Operator、Control、转换器或其他有效调用方仍使用的字符串检查、诊断、上下文发布和结果关联能力。**不得靠保留 `alg_types.h` 或给旧结构换名字来维持死路径。** + +### 2.6 内存、配置、并发与异常不变量 + +本次不修改 Operator 的资源机制。[S03][S04] + +| 不变量 | 实施与测试要求 | +| --- | --- | +| 输入同步借用 | 外部载体及其嵌套指针在 `Process` 返回前有效;不得因接口清理引入跨调用悬挂引用 | +| 输入转换 copy-in | 发布到请求黑板的值拥有所需数据,不将宿主不透明对象移入下层 | +| 输出池按现有帧深度工作 | 保留 `max_frame_depth` 当前默认 25、零值归一化及已有上限和预算规则;不引入 `frame_output_pool` 等新层 | +| 单份分配与池深度分离 | 分配方案只负责一份完整外部结构的分配、重置、销毁;现有池负责块数、租用和归还 | +| 配置解析在创建阶段 | 沿用 `allocator`、参数解析与已解析池规格;Process/输出转换器不重新读部署 JSON | +| 槽位名、类型后缀各司其职 | 保留 `key_suffix` 与 `type_suffix` 的既有区分,不能为了清理而合并;同一类型的不同逻辑槽仍独立解析 | +| 全批成功后发布 | Decode、分配、执行、Encode、发布中的失败不遗留本次租约、不把半批输出交给调用方 | +| 输出是句柄所属池的租约 | 持有 `shared_ptr` 不等于延长句柄或池内存寿命;需要长期保存时先复制字段,再释放所有租约 | +| 句柄销毁前置条件 | 调用方停止提交,并等待该句柄的 Process/Control 返回,释放所有输出引用,再 Destroy | +| Destroy 的消费语义 | 保留当前契约:有效句柄一经 Destroy 即被消费,即使因未归还输出返回错误,也不能重试或继续使用 | +| 同句柄与跨句柄并发 | 保留同句柄 Process/Control 串行化、不同句柄可并行;不宣称支持任意并发 Destroy/Deinit | +| 异常隔离 | 保留 Operator 边界的 `noexcept`、标准异常与未知异常屏障;删除 C ABI 屏障不等于删除共用诊断能力 | + +池满等待的回归测试必须由另一线程释放租约,并设置测试超时;不得让测试线程持有全部旧租约又同步等待新输出,制造无法结束的测试。 + +### 2.7 构建、公开头与动态符号 + +#### 2.7.1 公开头与目标 + +保留 `alg_sdk`、`llm_edgeflow::sdk`、库基础名 `company_alg_sdk`,以及现有分层对象目标。修改 `cmake_ext/LayerHeaderViews.cmake`,从 public、integration 视图移除两个 C API 头及旧 `alg_types.h`。不得为了修复包含错误,给公开 SDK 补上传递性的源码根目录或私有头目录。[S11] + +公开可调用头至少覆盖:Operator 的 `interface.h`、`types.h`,其需要的平台 Operator 类型头,以及日志、导出、版本和仍使用的错误码声明。新配置与增量重新配置后都不能出现残留的 C API 头视图。 + +不因删除 C11 算法接口而机械地将 `project(... LANGUAGES C CXX)` 改为仅 CXX。C 语言与第三方构建仍可能有关,且不属于本次宿主接口选择。 + +#### 2.7.2 导出由 12 项收敛为 6 项 + +修改 `cmake_ext/edgeflow_sdk.map.in` 和 `scripts/check_sdk_exports.sh`,同步删除六个旧算法符号:[S12] + +```text +Alg_Init +Alg_Create +Alg_Process +Alg_Control +Alg_Destroy +Alg_DeInit +``` + +最终保留的导出白名单为: + +| 类别 | 符号 / 函数 | +| --- | --- | +| Operator | `llm_edgeflow::operator_api::Get_LLM_EDGEFLOW_OperatorTable()` | +| Operator | `llm_edgeflow::operator_api::GetOperatorLastError()` | +| Operator | `llm_edgeflow::operator_api::ValidateOperatorConfigBinding(...)` | +| 独立日志 | `AlgBase_setLogLevelByName` | +| 独立日志 | `AlgBase_getLogLevelByName` | +| 独立日志 | `AlgBase_logPrint` | + +当前 Linux 导出门禁使用精确的 C++ 修饰名。对本次未改签名的三项沿用现有精确值,不用 `operator_api*` 或 `Alg*` 通配符扩大导出范围。跨平台采用各自现有工具链的符号约定;不能把一个平台的修饰名检查假称为所有平台验证。 + +三个日志函数不是第二套算法 API,保留它们符合本 RFC 的单入口目标。 + +#### 2.7.3 版本边界 + +删除已发布符号属于破坏性变更。建议在正式切换提交将产品版本设为 `11.0.0`、ABI 版本设为 `7.0.0`、`SOVERSION` 设为 `7`,并继续由当前 CMake 模板生成版本信息。[S11] + +不提供 ABI 6 版本节点、旧 SONAME 的兼容链接或旧 `Alg_*` 转发。Operator 虽然保留源码接口形状,宿主仍应按新 SDK 重新构建,并使用匹配的编译器/标准库与构建设置;函数表中的 STL 类型不是跨任意 C++ 工具链的稳定边界。本次不为此再加一层 C 包装。 + +### 2.8 不采用的替代方案 + +| 替代方案 | 不采用原因 | +| --- | --- | +| 只停止导出 `Alg_*`,保留内部实现、类型、注册和测试正向路径 | 只是隐藏接口,没有完成代码与维护面收敛 | +| `Alg_*` 转发到 Operator | 明确违背不保留兼容性要求 | +| `ENABLE_C_ABI` 默认关闭 | 仍需维护和测试两种产品形态 | +| 删除整个 Adapter 层 | 会误删 Operator 正在使用的转换、配置与验证能力 | +| 将所有转换器重新合并为业务 Adapter/bridge | 回退输入输出独立化成果,扩大设计与代码量 | +| 借机替换 Operator 输出池或公共 DTO | 无必要业务缺口,增加生命周期与内存风险 | +| 删除所有 `extern "C"` 和 `Company*` 类型 | 无法区分旧算法入口、独立日志与当前 Operator 数据声明 | + +## 3. 兼容与迁移 + +### 3.1 逐文件动作清单 + +下表用于实施与代码评审。标为“扫描”的项目表示纳入影响面,而非声称其中每个文件均已发现旧调用。 + +| 动作 | 文件 / 区域 | 完成条件 | +| --- | --- | --- | +| 删除 | `include/edgeflow/c_api.h` | 源树与公开头视图均不存在 | +| 删除 | `include/edgeflow/c_api.hpp` | 不再存在旧 `Alg_Process` C++ 包装 | +| 删除 | `src/adapter/c_api_adapter.cpp` | 无 `AlgHandleInstance` 和六个旧函数定义,构建源清单同步移除 | +| 删除 | `include/platform_mock/alg_types.h` | 所有有效消费者迁完,旧 C ABI 参数/业务结构无有效引用 | +| 局部删除 | `src/adapter/shared_algorithm_runtime.h/.cpp` | 仅移除 C ABI 专用方法和依赖,Operator 构建与控制仍工作 | +| 局部删除 | `include/adapter/io_converter.h` | 删除 C ABI 载体和 traits,保留 Operator 槽位、容量及端口契约 | +| 局部删除 | `src/adapter/input/*.cpp`、`output/*.cpp` | C ABI Decode/Encode/工厂/注册及失去用途的 helper 清零 | +| 修改 | `src/adapter/biz/*_bindings.cpp` | 只有 Operator 绑定,业务曝光审计不再要求 C ABI | +| 修改 | `src/adapter/io_converter_registry.cpp`、`io_binding_registry.cpp` | 非 Operator 注册被拒绝,其他冲突与完整性检查保留 | +| 修改 | `src/adapter/deployment_io_config.*`、`io_binding_resolver.*`、`operator/operator_config_resolver.*` | 无 C ABI 配置分支;Operator 校验、根路径、容量和预算不减弱 | +| 修改 | `CMakeLists.txt`、`src/adapter/CMakeLists.txt`、`cmake_ext/LayerHeaderViews.cmake` | 无旧源码/旧头声明,版本与层依赖一致 | +| 修改 | `cmake_ext/edgeflow_sdk.map.in`、`scripts/check_sdk_exports.sh` | 精确六项导出,旧算法符号不存在 | +| 清理 | `demo/common/operator_runner.h`、Operator 测试等残留 `c_api.h` include | 删除无用 include;需要错误码/版本的直接包含对应头 | +| 替换 | `tests/contract/abi/test_c11_abi_compliance.c` | 删除 C11 消费者,改由独立 C++ SDK 消费者验证公开边界 | +| 迁移 | `tests/contract/abi/test_c_abi_safety.cpp` | 有效安全场景迁入 Operator 套件;不将整份保障直接扔掉 | +| 同步 | `cmake_ext/TestInventory.cmake`、`Tests.cmake`、`IndividualTests.cmake`、`tests/CMakeLists.txt` | 两种运行模式都有迁移后的责任套件,无失效源、过滤器或缺失用例 | +| 扫描/修改 | `src/adapter/io_catalog.*`、`src/tools/`、`tools/`、`scripts/`、`dev_support/` | 生产工具不输出/生成/默认选择 C ABI;测试注册不会污染生产 SDK | +| 扫描/迁移 | `configs/`、`demo/fixtures/`、`tests/fixtures/` 及测试内动态配置生成 | 正向配置均为有效 Operator 组合;旧配置仅能作为明确的拒绝测试输入 | +| 扫描/修改 | `.github/workflows/`、静态/架构检查及脚手架模板 | 无旧目标要求和旧 API 生成路径;门禁强度不降低 | +| 同步文档 | 第 3.4 节所列现行指南 | 只描述当前 Operator + converter/binding 架构 | + +真正有用的共用文件应保留,不为了减少表中“修改”项而整文件删除,也不通过增加同等功能的替代文件来掩盖旧路径残留。 + +### 3.2 仓内消费者与测试迁移规则 + +#### 3.2.1 创建与执行 + +旧代码中的 `CompanyAlgParamCreate` 迁为现有 `CreateParam`,明确给出配置根 `model_path`、根内相对路径 `cfg_file_name`、设备与支持的计算平台。不要把原有任意路径接入方式照搬到 Operator,破坏根路径约束。 + +输入从 C ABI 结构迁为现有 Operator 结构,字符串按当前 `CompanyString` 长度/指针约定构造,使用 `MakeBorrowedOperatorInput` 填入对应命名槽位。载体及嵌套缓冲区应稳定存活到调用结束;构造测试批次时避免容器扩容使已保存的元素地址失效。 + +输出不再由调用方准备旧固定数组结构,而按现有约定提供与输入批次等长的输出容器,以及已声明的输出槽位请求。例如现有关键词调用使用 `client_channel.keyword_in` / `client_channel.keyword_out`,输出槽位先放空指针,再由成功的 `Process` 发布池租约。[S13] + +业务断言读取返回的 Operator 结构。对需要跨调用保存的内容做字段级深拷贝;不要只复制外层结构而保留池内字符串指针。释放输出容器之外,还要释放测试中复制出来的所有输出 `shared_ptr`,之后才能销毁句柄。 + +#### 3.2.2 控制与安全语义 + +旧 `CompanyAlgParamControl` 正向用例迁为现有类型化 Control 参数,或 `ControlCommand::kJson` 配合 `ControlJsonParam{cmd_id, payload}`。继续验证完整 JSON 对象、长度、命令声明、更新失败不破坏当前配置等已有保障,不创建第二种控制分发。 + +迁移测试时以**现有 Operator 的契约与错误语义**为准,不为复现被删除 C ABI 的错误优先级、输出计数或固定数组容量约定增加分支。 + +例如旧“调用方输出指针数组容量不足”测试不应原样保留;其有意义的保障分别映射为 Operator 批次数量/槽位检查,以及输出池字段容量不足时不越界、不发布、不泄漏租约的测试。 + +`shared_ptr` 无法凭任意裸内存证明真实对象类型。类型与后缀测试应针对当前可验证的槽位声明、ValueType 和合法载体契约,不能用不安全的内存探测或未定义行为充当类型安全测试。 + +### 3.3 配置与 Catalog 迁移 + +| 项目 | 迁移规则 | +| --- | --- | +| 已使用 Operator 的 `.conf` | 原样保留有效 binding、outputs、allocator、params 和容量;无需为本次换 schema | +| 仍使用 `*.cabi.*` 的仓内正向配置 | 选择对应已注册 Operator 绑定,并按实际外部输出槽补齐输出分配配置;逐项验证 | +| Pipeline JSON | 原则上不改算法、节点、提示词或连线;只处理确由废弃接入路径引出的引用问题 | +| 旧外部配置 | 明确失败,不自动把 `.cabi.` 替换成 `.operator.`,也不猜默认绑定 | +| 自定义/测试内注册 | 同步改为 Operator;负例在隔离测试中明确验证拒绝 | +| Catalog | 实际输入、输出转换器和绑定列表中不出现可用 C ABI 项;保留业务及内部端口信息 | +| CLI / Studio / 脚手架 | 不再提供或生成 C ABI 选项;旧参数值被明确拒绝,不退回 Operator 成功 | + +现有 `configs/pipeline_keyword_match_rules.conf` 已使用 `keyword_match.operator.v1` 和 `keyword_out` 输出池配置,可作为无模型权重的公开 SDK 冒烟基准。[S14] + +不能以一次全局文本替换完成配置迁移:旧 C ABI 固定输出结构和 Operator 输出池配置不同,同名业务也不代表两套载体和容量可以直接替换。 + +### 3.4 当前文档、治理与历史决策 + +实施必须同时修正文档中的外部业务边界:完整请求解码、字段选择、响应组装和序列化仍由 SDK 接入适配层完成,只是边界改为 Operator。Demo/Python 不得接管业务协议转换来掩盖缺失实现。 + +| 文档范围 | 必须同步的内容 | +| --- | --- | +| `README.md`、`doc/README.md`、`doc/architecture.md`、`doc/developer_guide.md` | 单一算法入口、当前调用链、版本与接入路径 | +| `AGENTS.md` | 删除“必须维护六个 Alg_*、公共算法头必须 C11、C ABI 为业务边界”的现行约束;保留层次隔离、异常隔离和完整载荷转换原则 | +| `doc/dev_guide/business_onboarding.md` | 按现有 input/output converter、binding、ValueType 和 Operator 编写;不再要求新增旧 IBizAdapter 或业务 bridge | +| `.agents/skills/llm-edgeflow-developer-guide/` 及其他实际相关指引 | 更新入口、实现位置、验证目标;不复制另一份互相冲突的架构规则 | +| `include/platform_mock/README.md`、`configs/README.md`、`tests/README.md` | 平台类型清单、部署配置规则、迁移后的测试责任 | +| `doc/solutions/translate.md`、输出分配指南及相关练习/模板 | 使用完整 Operator 外部请求;保留 JSON 业务语义和单份分配职责 | +| `tools/pipeline_studio/README.md`、工具帮助、架构图 | 不再把 C ABI 描述为可用算法入口 | +| `doc/CHANGELOG.md` | 明确列出破坏性删除、配置和源码迁移要求 | +| RFC 索引与 RFC-0059/对应实施计划 | 增加本 RFC 链接,注明仅取代双宿主入口部分,保留转换器独立化等有效决策 | + +历史 RFC、历史审计和旧版本变更记录不按关键词清空,不改写成“历史上只有 Operator”。采用明确的适用范围说明,区分历史事实与当前指南。[S18][S19] + +### 3.5 交付与回退边界 + +最终交付为一个完整的 Operator-only 变更集,不单独发布“入口已删除但测试、注册或工具仍要求 C ABI”的中间状态。 + +回退通过撤回完整变更集或恢复上一产品版本完成,而不是在新运行时内添加旧路径。已迁移到新版本的调用方和配置也必须与对应 SDK 一起回退;回退不代表本 RFC 需要维护长期兼容。 + +## 4. 验证与完成条件 + +### 4.1 现有测试的处置与保障迁移 + +测试文件与套件对应关系以当前 `TestInventory.cmake` 为准。[S15][S16] 下表区分“接口专属断言可删除”和“工程保障必须迁移”。 + +| 当前测试 / 责任 | 本次处置 | 迁移后必须证明 | +| --- | --- | --- | +| `C11AbiComplianceTest` / `test_c11_abi_compliance.c` | 删除旧正向消费者,增加下面定义的 `CppOperatorSdkTest` | 公开 C++ 头与共享库可以独立接入 | +| `CAbiSafetyTest` / `test_c_abi_safety.cpp` | 将仍有意义的安全场景迁入 Operator 责任套件;必要时重命名文件/套件 | 参数失败无资源泄漏、异常不越过公共边界、失败不发布输出 | +| `AdapterContractSecurityTest` | 保留责任,迁移旧载体和配置 | 输入边界、路径隔离、业务完整载荷与输出容量安全 | +| `DifferentIoModalitiesTest` | 改为 Operator 载体或复用已有覆盖 | 文本、音频、图像及其他已有模态的业务链不丢失 | +| `AllBizPipelinesTest` | 迁移创建、执行、结果提取 | 每个现有业务的有效路径、来源编号和结果语义 | +| `ConcurrencyAndEdgeCasesTest` | 迁移句柄与输出生命周期 | 同句柄序列化、不同句柄隔离、边界批次与错误回收 | +| `RuntimeControlAndHotSwapTest` | 使用现有 Operator Control | 规则、提示词、阈值/JSON 控制等实际支持的更新行为 | +| `OperatorApiTest`、`OperatorGoldenTest` | 保留并扩展缺失覆盖;清理无用 C API include | 原 Operator 行为未因删另一套接口而退化 | +| `OperatorOutputPoolTest`、`OperatorValueRegistryTest` | 保留,补必要的切换回归 | 单份分配、typed params、布局、容量、租约、销毁与失败回滚 | +| `IoConverterTest`、`TextConvertersTest`、`ComplexConvertersTest` | 删除 C ABI 正向组合;保留共享语义和 Operator 断言 | 完整外部协议转换、方向解耦、来源和结果一致性 | +| `IoBindingRegistryTest`、Catalog/注册冲突测试 | 收敛 transport,并增加拒绝注册测试 | 非 Operator 不可注册;缺失 Operator 能力仍 fail-closed | +| Demo、CLI、Studio 与脚手架测试 | 迁移旧生成内容和快照 | 用户正常路径可用,生产工具不再生成旧 API/配置 | +| 真实模型 E2E 源码 | 如包含旧调用则迁移;有资产时运行 | 不因可选测试默认不运行而留下不能编译的旧接口消费者 | + +复用已有责任套件,允许合并重复用例,但必须记录每个被移除场景的归宿。不能用“测试数量下降属于删接口”解释业务、内存或并发覆盖丢失。 + +### 4.2 新增最小公开 SDK 消费者测试 + +建议新增 `tests/contract/abi/test_cpp_operator_sdk.cpp`,注册为 `CppOperatorSdkTest`,替代原独立 C11 消费者的边界验证职责。 + +**构建约束:**单独可执行目标,只链接 `llm_edgeflow::sdk` 及必要的系统/测试依赖;只使用 SDK 公开头视图。不链接 `llm_edgeflow::internal_runtime`,不注入运行时对象或测试业务/后端注册,不添加源码根目录和 `src/` 作为补救包含路径。 + +**必须处理现有目录级继承:**基线 `tests/CMakeLists.txt` 在目录级调用了 `link_libraries(edgeflow_internal_headers)`。因此,仅给新目标写 `target_link_libraries(... llm_edgeflow::sdk)` 并不能证明它只有公开依赖。创建这个消费者时必须显式清除其继承的目录级链接依赖,或放入经过隔离的 public-only 测试作用域,再仅链接 SDK。不要为此取消其他内部测试需要的头视图。验收检查目标最终链接依赖和实际编译命令,确认没有 `edgeflow_internal_headers`、源码根/`src/`、extension 私有视图或内部对象注入。[S15] + +**执行流程:** + +```text +读取函数表并检查六个入口存在 + → Init + → 对生产关键词配置调用 ValidateOperatorConfigBinding + → Create(CPU,显式配置根与相对路径) + → Process(实际关键词规则,不依赖真实模型权重) + → 验证请求编号、状态与结果内容 + → Control 并再次验证结果变化 + → 复制结果字段,释放所有输出 shared_ptr + → Destroy + → Deinit +``` + +另增加最小公开头自包含编译检查:至少单独包含 `edgeflow/operator/interface.h`,并分别检查仍交付的类型、日志和版本头;不得先包含旧 C API 头补齐依赖。增加旧头不可获得/旧算法符号不可链接的边界检查。负向编译或链接测试必须先有同环境的正向对照,并确认失败原因确实是旧头/旧符号缺失,不能把缺少编译器或依赖误判为删除成功。 + +将 `CppOperatorSdkTest` 纳入共享 `TestInventory.cmake`,在 sharded 与 individual 模式中均运行。保留 `SdkExportSurfaceTest` 的独立职责;一个运行成功的 Demo 不足以代替它们,因为当前 Demo 使用内部运行时链接。[S13][S15] + +### 4.3 验收矩阵 + +| 编号 | 场景 | 必须观察到的结果 | +| --- | --- | --- | +| V01 | 源码与公开头边界 | 两个 C API 头、旧 adapter、旧平台 C ABI 类型删除;无有效正向调用 | +| V02 | 动态导出 | 六个旧 `Alg_*` 不存在;三个 Operator 与三个日志导出精确存在;无新增内部导出 | +| V03 | 注册初始化 | 生产绑定和转换器全为 Operator;所有已有业务应有的 Operator 组合完整;重复/缺失/非法注册仍失败 | +| V04 | 旧配置拒绝 | `*.cabi.*`、非 Operator transport、缺失显式绑定不被自动转换或默认补全 | +| V05 | 配置安全 | 根目录/相对路径约束、符号链接逃逸、未知字段、未知输出槽、非法容量与超预算仍被拒绝 | +| V06 | 请求与业务 | 关键词、实体、翻译、文档问答、精排、审计、音频和图像业务按各自可用 fixture 验证;输入输出协议与编号不变 | +| V07 | 槽位机制 | 当前支持的单/多输入输出、必需/可选槽、键后缀与类型后缀、同类型不同逻辑槽正常工作 | +| V08 | 内存失败路径 | 创建/解码/分配/执行/编码等错误不越界、不泄漏、本次输出不半发布;后续合法调用仍可用 | +| V09 | 租约 | 未释放结果在合法句柄生命周期内跨后续调用保持有效;释放后容量恢复;池满等待有可控唤醒 | +| V10 | 并发与控制 | 同句柄序列化、跨句柄隔离与已有控制更新语义保留;不把违反 Destroy 前置条件的行为当作正常并发使用 | +| V11 | 独立接入 | 只靠公开 SDK 的 C++ 消费者可以编译、链接并执行;旧头/符号的拒绝检查有正向对照 | +| V12 | 工具与治理 | Catalog、CLI、Studio、脚手架和现行文档只提供 Operator 算法路径,历史记录有明确边界 | +| V13 | 构建与测试清单 | 全新/增量构建与两种测试组织模式不缺套件;无失效 gtest filter 导致的空跑 | +| V14 | 分层回归 | Core、Node、Model、Backend 不新增 Operator 平台依赖;已有下层非相关契约测试保持通过 | + +V06 的无权重 fixture 证明链路与数据契约,不证明真实模型效果或目标硬件性能。涉及可选 Backend 的测试应明确记录构建开关、资产与跳过原因,不能把未启用算作通过。 + +### 4.4 残留扫描与误报边界 + +实施开始与结束各做一次仓库级扫描。以下为**实施者执行的定位命令**,本次文档编制没有执行该仓库扫描: + +```bash +git rev-parse HEAD + +git grep -n -I -E \ + 'edgeflow/c_api\.h(pp)?|platform_mock/alg_types\.h|Alg_(Init|Create|Process|Control|Destroy|DeInit)|CompanyAlgParam(Create|Control)|GetCAbi|DecodeCAbi|EncodeCAbi|cabi|CAbi|C_ABI|c_abi|C ABI' \ + -- include src demo dev_support tests configs cmake_ext scripts tools .github .agents \ + AGENTS.md README.md doc +``` + +`git grep` 返回 1 表示无匹配,返回大于 1 表示执行错误,自动门禁不得用无条件 `|| true` 混淆两者。 + +在已有静态门禁或其合适责任位置增加小范围检查即可,不建立新的审计框架。允许出现旧名称的位置必须显式列明,例如:本 RFC、保留原貌的历史记录、旧接口缺失/旧配置拒绝的负向测试,以及检查脚本自身的匹配规则。 + +**禁止将整个 `tests/`、`scripts/` 或 `doc/` 一概排除。** 当前指南、脚手架模板、正向测试和配置都属于需要收敛的范围。仅扫描字符串也不能证明无死代码;必须结合被删调用链、源清单、注册结果和动态符号检查。 + +### 4.5 实施时的验证命令 + +以下命令是交付要求,不是本次已经执行的结果。新测试名须先按第 4.2 节注册,实际 CTest 名称以更新后的 Inventory 为准。 + +**开发期:使用独立、无真实模型后端的构建目录定位接入层问题。** + +```bash +cmake -S . -B build-operator-only-dev -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=ON \ + -DENABLE_ONNXRUNTIME=OFF \ + -DENABLE_LLAMACPP=OFF \ + -DENABLE_WHISPERCPP=OFF \ + -DENABLE_KITELLM=OFF + +cmake --build build-operator-only-dev --parallel 4 + +ctest --test-dir build-operator-only-dev \ + --output-on-failure --no-tests=error \ + -R 'Operator|IoConverter|IoBinding|TextConverters|ComplexConverters|AdapterContractSecurity|SdkExportSurface' +``` + +该过滤器覆盖含 `Operator` 的新 SDK 测试名称;实际必须结合 Inventory 验证每个要求的套件均注册,而不能仅凭 CTest 总体“至少有一个测试”判定完整覆盖。 + +**最终交付:运行仓库现行完整门禁。** + +```bash +./scripts/run_all_tests.sh +``` + +该脚本在基线中自行完成格式、Shell、Git whitespace、默认后端构建及完整 CTest;默认启用 ONNX Runtime 与 llama.cpp,禁用可选真实模型 E2E。[S17] 无后端开发构建不能替代这个门禁。不要在完整门禁前后无目的重复同配置的全量构建与测试。 + +**额外验证:**在独立目录检查 `LLM_EDGEFLOW_SHARDED_TEST_RUNNERS=OFF` 的套件完整性与相关测试;另用 `ENABLE_SANITIZERS=ON`、`LLM_EDGEFLOW_SANITIZERS=address,undefined` 运行相关运行时用例。需要线程检查时使用独立的 `thread` sanitizer 构建,不与 ASan 混用。沿用现有 sanitizer 标签组织,确保池、Control 和相关运行时路径没有因迁移而漏出覆盖范围。 + +不得同时在同一构建目录运行竞争构建。验证记录必须标明提交、工具链、配置、命令、测试结果与未覆盖项。若评审后的代码修改影响已验证路径,旧结果不再充分,应重跑受影响验证,并确保最终候选提交满足完整交付门禁。 + +### 4.6 完成判定 + +只有同时满足下列条件才可将 RFC 改为 `Completed`: + +- [x] 旧六个算法函数、C++ 包装、C ABI 专用运行链、类型及生产注册已经实际删除,不是隐藏或禁用。 +- [x] 当前 Operator 的函数表、数据载体、配置与资源语义保留,没有新增兼容层、第二套 runtime 或新输出池体系。 +- [x] 所有有效消费者和正向配置迁移完成;旧配置与非法 transport 明确失败。 +- [x] 输入/输出转换独立、完整外部协议转换和四层依赖方向保持不变。 +- [x] 公开头与六项动态符号门禁通过,独立 C++ SDK 消费者验证通过。 +- [x] 被移除安全与业务测试均有明确处置记录,两种测试模式无缺失或空跑。 +- [x] 内存、池租约、失败回滚、Control 与并发回归通过,必要 sanitizer 结果有记录。 +- [x] Catalog、CLI、Studio、模板、CI、现行文档和 Agent 约束全部收敛;历史记录有清晰适用范围。 +- [x] 最终提交完成独立评审和完整交付门禁,未覆盖模型/硬件事项明确列出。 +- [x] 版本、RFC 索引与 Changelog 同步;没有伪装成新版本的旧 ABI 兼容产物。 + +## 5. 实施与最终结果 + +### 5.1 阶段划分与推进条件 + +建议每个阶段对应可单独评审的提交或提交组,按顺序推进。阶段验收失败时先修复本阶段,不以“后续会处理”为理由推进。此任务无需在生产代码中打桩或增加临时转发;阶段支点采用测试与清单。 + +| 阶段 | 主要工作 | 阶段验收与停止条件 | +| --- | --- | --- | +| P0:固定基线与迁移清单 | 在实施仓库核对 SHA 和与本 RFC 的差异;执行全仓定位;列出旧头、类型、函数、注册、配置、生成器和消费者;记录业务/安全测试场景映射;登记 RFC | 清单覆盖生产、工具、测试和文档;对新增差异作明确补充。未厘清的共用函数不能直接删除 | +| P1:解除依赖并建立替代验证 | 清理 Operator 与公共工具对 `c_api.h` 的间接依赖;将共用业务/安全/并发/Control 测试和有效消费者改用 Operator;增加独立 C++ SDK 消费者;保留尚未切除的原 C ABI 供原有专属测试运行 | Operator 相关构建和测试、公开 SDK 消费者通过;迁移后的业务断言未减少。不得用内部运行时链接冒充公开 SDK 验证 | +| P2:原子化删除旧接口链 | 同一可构建变更组内删除旧头、adapter、类型、专用 runtime 方法、转换分支与注册;收敛解析/审计;同步配置、CMake、测试清单、六项导出、建议版本及直接受门禁约束的文档/工具 | 没有有效 C ABI 路径;正向初始化与配置通过、旧配置拒绝;新/增量构建、导出和相关测试通过。不能只删源码却保留断裂构建或双 transport 审计 | +| P3:工具、模板与现行指南收尾 | 验证 Catalog/CLI/Studio/脚手架所有路径,补齐残留模板与文档;更新 AGENTS、技能引用、RFC 取代范围和 Changelog;核对两种测试模式与场景归宿 | 正常开发路径不再引导用户创建 C ABI;架构/文档/工具契约检查通过;无正向旧调用。P2 中必需的门禁修复不可拖到本阶段 | +| P4:回归、独立评审与关闭 | 完整门禁、必要非默认构建/sanitizer;只读评审删除闭包、所有权、注册审计和测试有效性;修复并验证发现的问题;在本 RFC 记录最终证据 | 满足第 4.6 节全部条件;有明确验证基线与未覆盖边界后才能标记 Completed | + +P1 暂时仍存在的原 C ABI 是尚未切除的基线代码,不是新增兼容产品。P2 后不得用兼容头、typedef、空实现或功能开关把它加回来。未完成整体迁移前不发布单独的“Operator-only”版本。 + +### 5.2 建议任务分工 + +主实施者负责删除边界、公共契约和注册/配置决策;机械修改可按输入、输出、绑定与文档区域分组,但必须先给出明确删除清单。测试负责人独立核对场景映射、公开 SDK 接入与失败路径。验证与评审应分别确认“执行通过”和“测试实际证明了需求”,不要仅凭净删代码量判断成功。 + +跨文件强耦合区域——旧类型删除、`io_converter.h`、注册白名单、共享 runtime 和构建清单——由同一负责人协调切换,避免并行修改互相依赖导致每个提交都无法构建。 + +### 5.3 风险与控制 + +| 风险 | 识别方式 | 控制措施 | +| --- | --- | --- | +| `c_api.h` 传递包含掩盖真实依赖 | 删除后错误码、版本宏或 DTO 未声明 | P1 先改为直接包含;公开头单独编译 | +| 混合转换器文件被整块误删 | Operator 业务/注册缺失或语义变化 | 按回调与注册逐项删;保留中性解析、输出组装与来源检查 | +| 只改生产业务声明,遗漏测试/生成器注册 | 全局 Init 冲突、脚手架重新生成 C ABI | 扫描静态与动态生成点;新增非 Operator 注册拒绝测试 | +| 内部对象链接掩盖公开 SDK 断裂 | 单元测试通过但宿主无法接入 | 独立链接 `llm_edgeflow::sdk` 的 C++ 消费者 | +| 输出租约迁移不完整 | Destroy 返回错误、结果悬挂或池满卡住 | 字段深拷贝、释放全部引用;超时保护的租约/并发回归 | +| 为删接口而弱化校验 | 未知 transport 跳过 outputs/预算审计 | 唯一 transport 约束与原必需校验共同落实;保留负例 | +| 旧文档将实现带回 Adapter/bridge | 新代码与现有转换器设计不一致 | 同步 AGENTS 和当前接入指南,历史 RFC 标明取代范围 | +| 版本、测试与源码基线漂移 | 实施分支已增加新接口或 RFC 编号 | P0 固定实际提交并记录差异,版本与编号入库前检查 | + +### 5.4 代码规模与设计约束 + +预期生产代码以删除为主:旧门面、固定 C 载体、重复转换分支和双注册将退出;新增代码主要应是公开 SDK 消费者及少量负向验收检查。 + +这是预期方向,不是可凭文档保证的净行数。实施后分别统计生产源码、测试、配置与文档的增删,解释保留的共用能力和新增测试。禁止为满足“删得更多”移除测试,也禁止新建与被删路径功能重复的抽象层抵消收敛效果。 + +### 5.5 本次文档交付的实际状态 + +| 项目 | 本次状态 | +| --- | --- | +| 核对默认分支、固定提交和关键接口源码 | 已完成远程静态核查与基线定位 | +| 阅读关键 runtime、转换器载体、注册、构建、测试清单及现行指南 | 已完成;依据列于附录 | +| 仓库全量调用点扫描、实际编译和测试运行 | **已执行**:全量门禁 `./scripts/run_all_tests.sh`(100/100 通过) | +| 修改仓库源代码、测试集、配置与架构图 | **已完成**:删除旧 C ABI,仅保留 C++ Operator SDK | +| 真实模型效果与目标硬件验收 | **未执行**:属于内网/模型资产就绪阶段工作,不影响单接口架构切换 | +| 当前结论 | **实施已全部完成**,达到 Completed 判定条件 | + +--- + +## 附录 A:固定提交源码依据 + +以下链接均固定到 `2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9`,用于区分“基线事实”和“本 RFC 的目标变更”。实施期间以实际分支新增差异补充,不把链接中的旧代码误当作迁移后的最终状态。 + +- **[S01] 旧算法 API 与包装**:[c_api.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/edgeflow/c_api.h)、[c_api.hpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/edgeflow/c_api.hpp)、[c_api_adapter.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/c_api_adapter.cpp)。 +- **[S02] Operator 公共入口**:[interface.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/edgeflow/operator/interface.h)、[types.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/edgeflow/operator/types.h)。 +- **[S03] 平台类型与生命周期契约**:[operator_types.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/platform_mock/operator_types.h)、[operator_data_types.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/platform_mock/operator_data_types.h)、[待删除的 alg_types.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/platform_mock/alg_types.h)。 +- **[S04] Operator 实际执行与资源管理**:[operator_adapter.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/operator/operator_adapter.cpp)。 +- **[S05] 共享运行时中的保留/删除边界**:[shared_algorithm_runtime.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/shared_algorithm_runtime.h)、[shared_algorithm_runtime.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/shared_algorithm_runtime.cpp)。 +- **[S06] 双载体视图与转换器作者契约**:[io_converter.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/adapter/io_converter.h)、[converter_authoring.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/include/adapter/converter_authoring.h)。 +- **[S07] 混合转换文件与业务双注册实例**:[text_input.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/input/text_input.cpp)、[keyword_match_bindings.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/biz/keyword_match_bindings.cpp)、[translate_bindings.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/biz/translate_bindings.cpp)。 +- **[S08] 注册与全量绑定审计**:[io_converter_registry.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/io_converter_registry.cpp)、[io_binding_registry.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/io_binding_registry.cpp)。 +- **[S09] 部署配置与计划解析**:[deployment_io_config.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/deployment_io_config.cpp)、[io_binding_resolver.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/io_binding_resolver.cpp)。 +- **[S10] 当前接入层源码清单**:[src/adapter/CMakeLists.txt](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/src/adapter/CMakeLists.txt)。 +- **[S11] 构建、版本与公开头视图**:[CMakeLists.txt](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/CMakeLists.txt)、[LayerHeaderViews.cmake](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/cmake_ext/LayerHeaderViews.cmake)。 +- **[S12] 当前 12 项动态导出**:[edgeflow_sdk.map.in](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/cmake_ext/edgeflow_sdk.map.in)、[check_sdk_exports.sh](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/scripts/check_sdk_exports.sh)。 +- **[S13] 已有 Operator Demo 路径**:[demo/main.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/demo/main.cpp)、[operator_runner.h](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/demo/common/operator_runner.h)、[keyword_match_demo.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/demo/biz/keyword_match_demo.cpp)、[demo/CMakeLists.txt](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/demo/CMakeLists.txt)。 +- **[S14] 无模型权重的 Operator 部署配置**:[pipeline_keyword_match_rules.conf](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/configs/pipeline_keyword_match_rules.conf)。 +- **[S15] 测试责任与构建组织**:[TestInventory.cmake](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/cmake_ext/TestInventory.cmake)、[Tests.cmake](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/cmake_ext/Tests.cmake)、[tests/CMakeLists.txt](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/tests/CMakeLists.txt)。 +- **[S16] 现有 Operator 业务结果断言**:[test_operator_golden.cpp](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/tests/integration/operator/test_operator_golden.cpp)。 +- **[S17] 完整交付门禁**:[run_all_tests.sh](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/scripts/run_all_tests.sh)。 +- **[S18] 需要同步的现行约束与指南**:[AGENTS.md](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/AGENTS.md)、[business_onboarding.md](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/doc/dev_guide/business_onboarding.md)、[README.md](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/README.md)。 +- **[S19] 输入输出独立化目标与取代范围**:[adapter_io_layout_design_2026-09-15.md](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/doc/plans/adapter_io_layout_design_2026-09-15.md)。 +- **[S20] RFC 编号、状态与结构约定**:[RFC 索引](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/doc/rfcs/README.md)、[RFC_TEMPLATE.md](https://github.com/chamsechan/LLM-EdgeFlow/blob/2d58d0c3dbadfe9b969f2eb85eb40461cbc7d0e9/doc/rfcs/RFC_TEMPLATE.md)。 diff --git a/doc/rfcs/README.md b/doc/rfcs/README.md index 9f603945..2e6d3442 100644 --- a/doc/rfcs/README.md +++ b/doc/rfcs/README.md @@ -25,7 +25,6 @@ | **RFC-0052** | 面向基础 C++ 开发者的 Node 作者接口重构 | `In Implementation` | 投产前 / Catalog v3 | 流程编排层、能力节点层 / Tooling / Docs | [0052-function-oriented-node-authoring.md](0052-function-oriented-node-authoring.md) | | **RFC-0054** | Control 作者接口与不可变配置快照 | `In Implementation` | 投产前 | 能力节点层 / Tooling / Docs | [0054-controlled-configuration-snapshots.md](0054-controlled-configuration-snapshots.md) | | **RFC-0057** | 降低 Pipeline 编排认知负担的实施方案 | `In Implementation` | 投产前 | 流程编排层 / Integration Tooling / Docs | [0057-pipeline-composition-experience.md](0057-pipeline-composition-experience.md) | -| **RFC-0059** | 输入输出转换独立化与接入绑定架构 | `In Implementation` | 投产前 / ABI 6.0.0 | 接入适配层、流程编排层 / Tooling / Docs | [0059-independent-adapter-input-and-output.md](0059-independent-adapter-input-and-output.md) | RFC-0054 是接续 RFC-0052 与已交付 RFC-0053、RFC-0055 的实施规格。`Proposed` 表示设计方案已形成,其中拟议 API、迁移与验证尚未实施,不作为当前 SDK 功能说明。 @@ -85,6 +84,8 @@ RFC-0054 是接续 RFC-0052 与已交付 RFC-0053、RFC-0055 的实施规格。` | **RFC-0055** | 批次关联、分组、选择回填与拆分公共工具 | `Completed` | 投产前 / Catalog v3 | 能力节点层 / Tooling / Docs | [0055-traceable-batch-operations.md](0055-traceable-batch-operations.md) | | **RFC-0056** | 投产前框架兼容入口与配置收口(第一批瘦身落地) | `Completed` | 投产前 | 四层 / Build / Tooling / Docs | [0056-batch-1-framework-slimming.md](0056-batch-1-framework-slimming.md) | | **RFC-0058** | 投产前诊断身份与 Node 注册状态收敛 | `Completed` | 投产前 | 流程编排层、能力节点层、接入适配层 / Tooling | [0058-diagnostic-and-node-registry-convergence.md](0058-diagnostic-and-node-registry-convergence.md) | +| **RFC-0059** | 输入输出转换独立化与接入绑定架构 | `Completed` | 投产前 / ABI 6.0.0 | 接入适配层、流程编排层 / Tooling / Docs | [0059-independent-adapter-input-and-output.md](0059-independent-adapter-input-and-output.md)(宿主双入口部分被 RFC-0060 取代) | +| **RFC-0060** | 删除 C ABI,仅保留 C++ Operator API | `Completed` | `v11.0.0` / ABI 7.0.0 | 接入适配层、流程编排层 / Tooling / Docs | [0060-cpp-operator-only.md](0060-cpp-operator-only.md) | ## 专项验收与评审归档 diff --git a/doc/solutions/translate.md b/doc/solutions/translate.md index 8b21295d..2f279e6c 100644 --- a/doc/solutions/translate.md +++ b/doc/solutions/translate.md @@ -1,35 +1,44 @@ # JSON 字符串翻译方案 -输入输出以 C ABI 边界为准,遵循[统一接入约定](../dev_guide/business_onboarding.md#输入输出以-c-abi-为边界)。 -完整请求 JSON 字符串进入 SDK,翻译 Adapter 在接入适配层只提取 `query`,通过现有 -模型进行一次生成,只返回简体中文译文原句;C++ Adapter 再用 JSON 序列化器组装 +输入输出以 Operator 接口为边界,遵循[统一接入约定](../dev_guide/business_onboarding.md)。 +完整请求 JSON 字符串进入 SDK,翻译 InputConverter 在接入适配层只提取 `query`,通过现有 +模型进行一次生成,只返回简体中文译文原句;OutputConverter 再用 JSON 序列化器组装 仅含 `translated` 字符串的完整响应,返回宿主。 `version`、`endpoint`、`src_lan` 和其他字段不参与路由、类型检查或提示词构造。 -## 直接调用 C ABI +## 直接调用 Operator API -翻译句柄使用 `ALG_BIZ_TYPE_TRANSLATE`,Pipeline 的 `biz_name` 为 `translate_v1`。 +翻译部署配置使用 `configs/pipeline_translate_cpu.conf`,其 `io_binding` 为 `translate.operator.v1`,Pipeline 的 `biz_name` 为 `translate_v1`。 输入/输出复用已有文本载体,因此结构字段仍叫 `sentence_text` / `entities_json`。 传入的是完整对象文本,不是 `query` 子串,也不是双重 JSON 编码后的字符串: -```c -CompanyAlgParamCreate create = { - "configs/pipeline_translate_cpu.json", "./models", 0, - ALG_BIZ_TYPE_TRANSLATE -}; -CompanyEntityInputStruct input = { - 1, - "{\"version\":\"0.0.1\",\"endpoint\":\"translate\"," - "\"query\":\"hello,what is your name\",\"src_lan\":\"en\"}" -}; -CompanyEntityOutputStruct output = {0}; -const void* inputs[] = {&input}; -void* outputs[] = {&output}; -int count = 1; -/* Alg_Init、Alg_Create(&handle, &create) 成功后: */ -int ret = Alg_Process(handle, inputs, 1, outputs, &count); -/* ret == 0 时,output.entities_json 是完整的 {"translated":"..."}。 - 使用后 Alg_Destroy(handle)、Alg_DeInit;检查各接口返回值。 */ +```cpp +auto op = llm_edgeflow::operator_api::Get_LLM_EDGEFLOW_OperatorTable(); +op.Init(); + +llm_edgeflow::operator_api::CreateParam create{}; +create.model_path = "."; +create.cfg_file_name = "configs/pipeline_translate_cpu.conf"; +create.compute_platform = llm_edgeflow::operator_api::ComputePlatform::kCpu; + +void* handle = nullptr; +op.Create(&handle, &create); + +std::string req_json = "{\"version\":\"0.0.1\",\"endpoint\":\"translate\"," + "\"query\":\"hello,what is your name\",\"src_lan\":\"en\"}"; +CompanyString cs{static_cast(req_json.size()), const_cast(req_json.data())}; +CompanyOperatorEntityInput input{1, &cs}; + +llm_edgeflow::operator_api::NamedIoBatch inputs(1); +inputs[0]["trans.entity_in"] = llm_edgeflow::operator_api::MakeBorrowedOperatorInput(&input); + +llm_edgeflow::operator_api::NamedIoBatch outputs(1); +outputs[0]["trans.entity_out"] = nullptr; + +int ret = op.Process(handle, inputs, outputs); +/* ret == 0 时,outputs[0]["trans.entity_out"] 指向 CompanyOperatorEntityOutput, + 其 entities_json 是完整的 {"translated":"..."}。 + 复制需要的值,重置 outputs/租约后,调用 op.Destroy(handle)、op.Deinit()。 */ ``` `input.sentence_text` 的实际内容是: @@ -62,38 +71,35 @@ stdout 每行输出一个完整响应 JSON;日志路径写 stderr。包装层 `input.txt`(完整请求)、`demo.log` 和 `results/translate/` 下的原生结果/汇总。 也可通过 stdin 输入一个完整请求,或用 `--output-dir` 指定保存目录。 -统一 Demo 走 Operator 外观,与 C ABI 共用同一个 Translate Adapter;它不会调用导出的 -`Alg_Process`。可直接去掉 Python 层运行同一方案: +统一 Demo 走 Operator 外观: ```bash ./build/alg_demo --biz translate --config configs/pipeline_translate_cpu.conf \ --dataset data/corpus_translate.jsonl --output-dir results/translate-native ``` -上面的 C ABI 示例和直接调用 `Alg_Process` 的契约测试单独证明 C 入口的行为。 +上面的 Operator 示例和测试单独证明 Operator 接口的行为。 ## 复用范围与处理边界 ```mermaid flowchart LR - A[完整请求 JSON] --> B[C ABI / Operator] - B --> C[C++ Adapter:解析并读取 query] + A[完整请求 JSON] --> B[Operator API] + B --> C[InputConverter:解析并读取 query] C --> E[LlmGenerateNode:一次生成,只返回译文] - E --> G[C++ Adapter:序列化 translated 响应] + E --> G[OutputConverter:序列化 translated 响应] G --> H[完整响应 JSON] ``` -- [Translate Adapter](../../src/adapter/biz/translate_adapter.cpp)复用现有 Entity Adapter - 的指针/长度校验、copy-in、来源映射和 C/Operator 两种输出打包,只增加 JSON 字段映射。 - [bridge](../../src/adapter/biz/translate_operator_bridge.cpp)复用 `entity_in/entity_out` - 宿主类型和输出池;统一 Demo 复用现有文本/JSON 运行函数。 +- [Translate InputConverter](../../src/adapter/input/translate_json_input.cpp) 与 [OutputConverter](../../src/adapter/output/translation_json_output.cpp) + 复用 `entity_in/entity_out` 宿主类型和输出池,只增加 JSON 字段映射;统一 Demo 复用现有文本/JSON 运行函数。 - [Pipeline](../../configs/pipeline_translate_cpu.json)仅使用已有 `LlmGenerateNode`, 直接将 `input_sentences` 原文传入模型,生成纯文本 `llm_answers`。翻译规则放在模型 `system_prompt` 配置中,由现有 C++ Model 组装对话提示词。每条请求只调用一次文本 生成,没有格式修复或二次推理;自回归生成内部仍逐 token 解码。无关字段不会进入模型。 - [部署配置](../../configs/pipeline_translate_cpu.conf)选择既有 Qwen Model / llama.cpp Backend;新增业务仅涉及 Integration 的注册与转换,Core、节点、模型实现和现有 - 实体业务语义保持不变。决策见 [RFC-0048](../rfcs/0048-translation-json-abi.md)。 + 实体业务语义保持不变。决策见 [RFC-0048](../rfcs/0048-translation-json-abi.md) 与 [RFC-0060](../rfcs/0060-cpp-operator-only.md)。 输入必须是 JSON 对象且 query 是字符串;非法输入在 SDK 内报错。空串可通过接入校验, 但当前 Qwen Model 拒绝空原文并返回错误,不补造成功译文。 @@ -102,7 +108,7 @@ Adapter 原样保存模型文本,不解析、裁剪或去掉引号;即使文 `translated` 的字符串内容。输入与输出中的换行、引号、反斜杠和 NUL 通过解析/序列化保真。 无静态默认译文或成功 fallback。错误状态、来源异常或容量不足返回非零。 -完整输入沿用 64 KiB 上限。C ABI 输出数组为 2048 字节(含结尾 NUL),超出报错; +完整输入沿用 64 KiB 上限。 Operator 的 JSON 输出池在本配置中为 8191 字节。当前上下文为 2048 tokens、生成上限为 512 tokens,用于短文本演示。长文需调整并验证上下文、生成长度和输出容量;C++ 保证 响应格式,译文准确性、完整性和是否遵守提示词仍取决于模型。 @@ -117,16 +123,16 @@ Operator 的 JSON 输出池在本配置中为 8191 字节。当前上下文为 2 ./scripts/run_all_tests.sh ``` -现有 Adapter 套件直接调用 `Alg_Process`,使用计数测试模型验证原始 query 输入、 +现有测试套件直接调用 Operator Process,使用计数测试模型验证原始 query 输入、 每条请求仅一次生成、C++ 组装完整响应,以及无关字段忽略、转义、失败与输出容量。 该测试不作为翻译质量证据。Python 测试验证完整对象转发和完整 SDK 响应转发; -真实模型验证使用同一份生产 Pipeline,分别执行直接 C ABI 调用和三条样例的 Operator Demo。 +真实模型验证使用同一份生产 Pipeline,执行三条样例的 Operator Demo。 -本次真实 C ABI 请求返回 `ret=0`、`count=1`、`request_id=101`、`status_code=0`, +真实请求返回 `ret=0`、`request_id=101`、`status_code=0`, 响应如上。Demo 的请求 ID 30001–30003 状态均为 0,译文分别为“你好,你叫什么名字?”、 -“早上好。”和“谢谢!”。同句柄输入 `{"query":""}` 实测返回 `-1`,没有有效成功响应。 +“早上好。”和“谢谢!”。同句柄输入 `{"query":""}` 实测返回错误,没有有效成功响应。 复用相同输入/输出契约的新提示词方案只需改配置;改变 SDK 字段契约时,按 [业务接入指南](../dev_guide/business_onboarding.md)在 Integration 增加必要转换。 -不要在 Demo 外预先提取字段来替代 C ABI 能力。后续指导见 +不要在 Demo 外预先提取字段来替代 Operator 能力。后续指导见 [json-prompt-solution skill](../../.agents/skills/json-prompt-solution/SKILL.md)。 diff --git a/include/adapter/adapter_batch.h b/include/adapter/adapter_batch.h index 32a4d56d..e0a2adc6 100644 --- a/include/adapter/adapter_batch.h +++ b/include/adapter/adapter_batch.h @@ -15,7 +15,7 @@ #include "adapter/text_carrier.h" #include "core/alg_context.h" #include "core/blackboard_key.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { diff --git a/include/adapter/adapter_result.h b/include/adapter/adapter_result.h index 6da53c29..47264644 100644 --- a/include/adapter/adapter_result.h +++ b/include/adapter/adapter_result.h @@ -4,7 +4,7 @@ #include #include "adapter/adapter_status.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { diff --git a/include/adapter/adapter_status.h b/include/adapter/adapter_status.h index f03c9617..27181754 100644 --- a/include/adapter/adapter_status.h +++ b/include/adapter/adapter_status.h @@ -4,7 +4,7 @@ #include #include -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { @@ -14,7 +14,7 @@ namespace llm_edgeflow { enum class OwnershipPolicy { kCopyIn = 0, // 默认策略:Unpack 将外部数据完整复制到内部 DTO/Buffer kBorrowDuringProcess = - 1, // 借用策略:仅在本次同步 Alg_Process 期间借用指针,调用返回后绝不留存 + 1, // 借用策略:仅在本次同步 Process 期间借用指针,调用返回后绝不留存 kRetainWithCallback = 2 // 跨调用异步持有 (暂未开放) }; diff --git a/include/adapter/adapter_validation_helper.h b/include/adapter/adapter_validation_helper.h index 4c45e30a..e07c31f5 100644 --- a/include/adapter/adapter_validation_helper.h +++ b/include/adapter/adapter_validation_helper.h @@ -7,96 +7,25 @@ #include "adapter/adapter_status.h" #include "core/alg_context.h" -#include "edgeflow/c_api.h" #include "edgeflow/log.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { /** * @brief 业务适配器通用输入输出批量契约校验工具类 (接入适配层内部) * - * 契约规则与安全解析工具集 (ADP-001, ADP-002, ADP-005, REV2-002, REV2-005): - * 1. ValidateBatchPreFlight (执行前严苛预检): - * - 必须在 Pipeline Execute 前调用,杜绝容量不足时无效执行模型推理; - * - 检查 num_inputs > 0 且不超过 max_batch_size (超出确定性返回 -3); - * - 检查每一个 inputs[i] 非空 (包含空槽位确定性返回 -3); - * - 检查 *num_outputs 容量 >= required_count (不足时回填所需容量并确定性返回 - * -4); - * - 检查每一个 outputs[i] 非空 (包含空槽位确定性返回 -4)。 - * 2. 字段级安全解析工具 (ADP-001, ADP-005, RECHECK-001, RECHECK-004): - * - RequireNotNull: 非空指针检查与字段路径诊断; - * - RequireRange: 数值区间边界检查; - * - RequireEnum: 枚举值有效性与 Tagged Union 校验; - * - RequireBoundedString: 有界安全字符串扫描与长度校验 (防止无界内存扫描); - * - CheckedMultiply: 乘法溢出与最大缓冲区字节限制; - * - CheckedStringCopy: 字符串安全拷贝,截断时返回 false 并记录 - * BufferTooSmall 诊断。 + * 字段级安全解析工具集 (ADP-001, ADP-002, ADP-005, RECHECK-001, RECHECK-004): + * - RequireNotNull: 非空指针检查与字段路径诊断; + * - RequireRange: 数值区间边界检查; + * - RequireEnum: 枚举值有效性与 Tagged Union 校验; + * - RequireBoundedString: 有界安全字符串扫描与长度校验 (防止无界内存扫描); + * - CheckedMultiply: 乘法溢出与最大缓冲区字节限制; + * - CheckedStringCopy: 字符串安全拷贝,截断时返回 false 并记录 BufferTooSmall + * 诊断。 */ class AdapterValidationHelper { public: - static int ValidateBatchPreFlight(const void** inputs, int num_inputs, - void** outputs, int* num_outputs, - int max_batch_size, int required_count, - const char* adapter_name) { - if (!inputs || num_inputs <= 0) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight failed: Invalid inputs array or " - "num_inputs <= 0 (%d)\n", - adapter_name ? adapter_name : "Biz", num_inputs); - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - // REV2-005: 强制校验 max_batch_size Descriptor 契约 - if (max_batch_size > 0 && num_inputs > max_batch_size) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight failed: num_inputs (%d) exceeds " - "max_batch_size limit (%d)\n", - adapter_name ? adapter_name : "Biz", num_inputs, max_batch_size); - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < num_inputs; ++i) { - if (!inputs[i]) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight failed: Null pointer at input " - "index [%d]\n", - adapter_name ? adapter_name : "Biz", i); - return COMPANY_ALG_ERR_INVALID_INPUT; - } - } - - if (!num_outputs || *num_outputs < 0) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight failed: Invalid num_outputs " - "pointer or negative capacity\n", - adapter_name ? adapter_name : "Biz"); - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - // REV2-002: 提前拦截容量不足或空 outputs (标准容量预查),回填所需容量 - int capacity = *num_outputs; - if (capacity < required_count || !outputs) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight: Output capacity (%d) " - "insufficient or outputs array null for required count (%d)\n", - adapter_name ? adapter_name : "Biz", capacity, required_count); - *num_outputs = required_count; // 报告所需容量 - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - for (int i = 0; i < required_count; ++i) { - if (!outputs[i]) { - ALG_LOG_ERROR( - "[AdapterValidation] %s PreFlight failed: Null pointer at output " - "slot index [%d]\n", - adapter_name ? adapter_name : "Biz", i); - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - return COMPANY_ALG_SUCCESS; - } - static int ValidateBatchInputs(const void** inputs, int num_inputs, int max_batch_size = 64, const char* adapter_name = nullptr) { diff --git a/include/adapter/biz_results.h b/include/adapter/biz_results.h index 4a610936..1b5fcd03 100644 --- a/include/adapter/biz_results.h +++ b/include/adapter/biz_results.h @@ -4,7 +4,7 @@ #include namespace llm_edgeflow { -// Results owned by Integration. Strings have no C ABI array limit; the +// Results owned by Integration. Strings have no fixed array limit; the // configured Operator output pool remains the authoritative external capacity // boundary. diff --git a/include/adapter/io_binding.h b/include/adapter/io_binding.h index 51e5e5a3..8508182e 100644 --- a/include/adapter/io_binding.h +++ b/include/adapter/io_binding.h @@ -13,7 +13,7 @@ namespace llm_edgeflow { struct IoBindingDefinition { std::string binding_id; std::string biz_name; - std::string transport; // "cabi" 或 "operator" + std::string transport; // "operator" std::string input_converter_id; std::string output_converter_id; std::unordered_map @@ -29,7 +29,7 @@ struct IoBindingDefinition { struct BizExposureDefinition { std::string biz_name; size_t max_batch_size = 64; - std::vector required_transports; // {"cabi", "operator"} + std::vector required_transports; // {"operator"} }; } // namespace llm_edgeflow diff --git a/include/adapter/io_converter.h b/include/adapter/io_converter.h index c0e81ac4..bbe99231 100644 --- a/include/adapter/io_converter.h +++ b/include/adapter/io_converter.h @@ -14,13 +14,12 @@ #include "core/blackboard_key.h" #include "core/port_definition.h" #include "core/validated_node_plan.h" -#include "platform_mock/alg_types.h" #include "platform_mock/operator_data_types.h" namespace llm_edgeflow { /** - * @brief 外部类型标识萃取器 (SSOT Type Traits for C ABI / Operator structs) + * @brief 外部类型标识萃取器 (SSOT Type Traits for Operator structs) */ template struct ExternalTypeTraits { @@ -33,33 +32,6 @@ struct ExternalTypeTraits { static constexpr const char* TypeName() { return Name; } \ } -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyAuditInputStruct, - "CompanyAuditInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyAuditOutputStruct, - "CompanyAuditOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyKeywordInputStruct, - "CompanyKeywordInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyKeywordOutputStruct, - "CompanyKeywordOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyEntityInputStruct, - "CompanyEntityInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyEntityOutputStruct, - "CompanyEntityOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyDocInputStruct, "CompanyDocInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyDocOutputStruct, "CompanyDocOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyOcrDocInputStruct, - "CompanyOcrDocInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyOcrDocOutputStruct, - "CompanyOcrDocOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyAudioInputStruct, - "CompanyAudioInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyAudioOutputStruct, - "CompanyAudioOutputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyRerankBatchInputStruct, - "CompanyRerankBatchInputStruct"); -DECLARE_EXTERNAL_TYPE_TRAITS(CompanyRerankBatchOutputStruct, - "CompanyRerankBatchOutputStruct"); - DECLARE_EXTERNAL_TYPE_TRAITS(CompanyString, "CompanyString"); DECLARE_EXTERNAL_TYPE_TRAITS(CompanyBuffer, "CompanyBuffer"); DECLARE_EXTERNAL_TYPE_TRAITS(CompanyAny, "CompanyAny"); @@ -96,31 +68,37 @@ DECLARE_EXTERNAL_TYPE_TRAITS(int, "int"); */ class ExternalInputBatchView { public: - // C ABI 纯指针数组 - const void** items = nullptr; size_t count = 0; std::string type_id; // Operator 具名槽位输入: slot_name -> vector of shared_ptr std::unordered_map>> slots; + // Operator 具名槽位借用输入 (非拥有指针): slot_name -> vector of const void* + std::unordered_map> leased_slots; std::unordered_map slot_types; template - const T* GetCAbi(size_t index) const { - if (!items || index >= count) return nullptr; - if constexpr (!std::is_void_v) { - if (!type_id.empty()) { - const char* expected = ExternalTypeTraits::TypeName(); - if (!expected || type_id != expected) { - return nullptr; + const T* GetSlot(const std::string& slot_name, size_t index) const { + auto lit = leased_slots.find(slot_name); + if (lit != leased_slots.end() && index < lit->second.size()) { + if constexpr (!std::is_void_v) { + std::string expected; + auto st_it = slot_types.find(slot_name); + if (st_it != slot_types.end()) { + expected = st_it->second; + } else if (!type_id.empty()) { + expected = type_id; + } + if (!expected.empty()) { + const char* actual_trait = ExternalTypeTraits::TypeName(); + if (!actual_trait || expected != actual_trait) { + return nullptr; + } } } + return static_cast(lit->second[index]); } - return static_cast(items[index]); - } - template - const T* GetSlot(const std::string& slot_name, size_t index) const { auto it = slots.find(slot_name); if (it == slots.end() || index >= it->second.size()) return nullptr; if constexpr (!std::is_void_v) { @@ -140,14 +118,6 @@ class ExternalInputBatchView { } return static_cast(it->second[index].get()); } - - template - const T* At(size_t index, const std::string& slot_name = "") const { - if (!slot_name.empty()) { - return GetSlot(slot_name, index); - } - return GetCAbi(index); - } }; /** @@ -155,10 +125,7 @@ class ExternalInputBatchView { */ class ExternalOutputBatchView { public: - // C ABI 输出指针数组 - void** items = nullptr; size_t count = 0; - size_t capacity = 0; std::string type_id; // Operator 已租用输出块: slot_name -> vector of void* @@ -183,21 +150,6 @@ class ExternalOutputBatchView { return nullptr; } - template - T* GetCAbi(size_t index) const { - size_t limit = capacity > 0 ? capacity : count; - if (!items || index >= limit) return nullptr; - if constexpr (!std::is_void_v) { - if (!type_id.empty()) { - const char* expected = ExternalTypeTraits::TypeName(); - if (!expected || type_id != expected) { - return nullptr; - } - } - } - return static_cast(items[index]); - } - template T* GetSlot(const std::string& slot_name, size_t index) const { const std::vector* vec = nullptr; @@ -266,7 +218,7 @@ class ExternalOutputBatchView { struct InputDecodeOptions { std::string binding_id; std::string converter_id; - std::string transport; // "cabi" 或 "operator" + std::string transport; // "operator" size_t max_batch_size = 64; }; @@ -276,7 +228,7 @@ struct InputDecodeOptions { struct OutputEncodeOptions { std::string binding_id; std::string converter_id; - std::string transport; // "cabi" 或 "operator" + std::string transport; // "operator" size_t max_batch_size = 64; }; @@ -360,7 +312,7 @@ class OutputPortBindings { }; /** - * @brief 外部槽位定义 (C ABI 结构体或 Operator 槽位) + * @brief 外部槽位定义 (Operator 槽位) */ struct ExternalSlotDefinition { std::string slot_name; @@ -411,7 +363,7 @@ using EncodeOutputFn = int (*)(AlgContext* context, */ struct InputConverterDefinition { std::string converter_id; - std::string transport; // "cabi" 或 "operator" + std::string transport; // "operator" std::string schema_id; int schema_version = 1; std::string external_type; @@ -428,7 +380,7 @@ struct InputConverterDefinition { */ struct OutputConverterDefinition { std::string converter_id; - std::string transport; // "cabi" 或 "operator" + std::string transport; // "operator" std::string schema_id; int schema_version = 1; std::string external_type; diff --git a/include/adapter/text_carrier.h b/include/adapter/text_carrier.h index 35e8bfec..5aac2a47 100644 --- a/include/adapter/text_carrier.h +++ b/include/adapter/text_carrier.h @@ -8,8 +8,9 @@ #include "adapter/adapter_status.h" #include "adapter/adapter_validation_helper.h" #include "adapter/biz_results.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" +#include "platform_mock/error_codes.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { @@ -54,59 +55,23 @@ inline int UnpackTextCarrierBatch(const void** inputs, int num_inputs, out_requests->clear(); out_requests->reserve(num_inputs); for (int i = 0; i < num_inputs; ++i) { - const auto* in = static_cast(inputs[i]); + const auto* in = static_cast(inputs[i]); if (!AdapterValidationHelper::RequireNotNull("inputs[i]", in, i, adapter_name, out_status)) { return COMPANY_ALG_ERR_INVALID_INPUT; } - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].sentence_text", in->sentence_text, - kMaxTextCarrierSentenceLen, i, adapter_name, out_status)) { + if (!in->sentence_text || in->sentence_text->length < 0 || + (in->sentence_text->length > 0 && !in->sentence_text->data)) { return COMPANY_ALG_ERR_INVALID_INPUT; } - out_requests->emplace_back(in->request_id, std::string(in->sentence_text)); - } - return COMPANY_ALG_SUCCESS; -} - -/** - * @brief 文本载体输出受检写入 (支持固定 C 结构体与 owned Result) - */ -template -inline int WriteTextCarrierOutput(Output* out, uint64_t request_id, - int status_code, const std::string& text, - int sample_idx, const char* adapter_name, - AdapterStatus* out_status); - -template <> -inline int WriteTextCarrierOutput( - CompanyEntityOutputStruct* out, uint64_t request_id, int status_code, - const std::string& text, int sample_idx, const char* adapter_name, - AdapterStatus* out_status) { - if (!out) return COMPANY_ALG_ERR_INVALID_INPUT; - out->request_id = request_id; - out->status_code = status_code; - if (!AdapterValidationHelper::CheckedStringCopy( - out->entities_json, sizeof(out->entities_json), text.c_str(), - "outputs[i].entities_json", sample_idx, adapter_name, out_status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; + if (static_cast(in->sentence_text->length) > + kMaxTextCarrierSentenceLen) { + return COMPANY_ALG_ERR_INVALID_INPUT; + } + out_requests->emplace_back( + in->request_id, + std::string(in->sentence_text->data, in->sentence_text->length)); } return COMPANY_ALG_SUCCESS; } - -template <> -inline int WriteTextCarrierOutput( - EntityResult* out, uint64_t request_id, int status_code, - const std::string& text, int sample_idx, const char* adapter_name, - AdapterStatus* out_status) { - (void)sample_idx; - (void)adapter_name; - (void)out_status; - if (!out) return COMPANY_ALG_ERR_INVALID_INPUT; - out->request_id = request_id; - out->status_code = status_code; - out->entities_json = text; - return COMPANY_ALG_SUCCESS; -} - } // namespace llm_edgeflow diff --git a/include/edgeflow/c_api.h b/include/edgeflow/c_api.h deleted file mode 100644 index 2253f861..00000000 --- a/include/edgeflow/c_api.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef COMPANY_ALG_INTERFACE_H_ -#define COMPANY_ALG_INTERFACE_H_ - -#include -#include - -#include "edgeflow/export.h" -#include "edgeflow/version.h" -#include "platform_mock/alg_types.h" -#include "platform_mock/error_codes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __cplusplus -#define COMPANY_ALG_NOEXCEPT noexcept -#else -#define COMPANY_ALG_NOEXCEPT -#endif - -// ------------------------------------------------------------- -// 本项目实现的六个 C ABI 入口,当前使用本地平台模拟参数类型。 -// ------------------------------------------------------------- - -/** - * @brief 全局资源初始化 (进程级) - */ -COMPANY_ALG_API int Alg_Init(void) COMPANY_ALG_NOEXCEPT; - -/** - * @brief 创建算法处理句柄实例 (会话级) - * @param[out] hndl 返回的句柄指针 - * @param[in] param_create 创建参数 - */ -COMPANY_ALG_API int Alg_Create(void** hndl, - const CompanyAlgParamCreate* param_create) - COMPANY_ALG_NOEXCEPT; - -/** - * @brief 批量执行算法计算 (纯 C 标准批处理接口) - * @param[in] hndl 算法句柄 - * @param[in] inputs 多个输入结构体指针数组 - * @param[in] num_inputs 输入样本数量 - * @param[out] outputs 多个输出结构体指针数组 - * @param[in,out] num_outputs 输入为 outputs 容量,输出为实际填充的样本数量 - * @note 同一 hndl 上的 Alg_Process 与 Alg_Control - * 由实现串行执行;不同句柄可并行。 - */ -COMPANY_ALG_API int Alg_Process(void* hndl, const void** inputs, int num_inputs, - void** outputs, - int* num_outputs) COMPANY_ALG_NOEXCEPT; - -/** - * @brief 运行时动态控制或参数调整 - * @note 与同一 hndl 上的 Alg_Process/Alg_Control 串行执行。 - */ -COMPANY_ALG_API int Alg_Control(void* hndl, - const CompanyAlgParamControl* param_control) - COMPANY_ALG_NOEXCEPT; - -/** - * @brief 销毁算法句柄实例 - * @pre 调用方必须先停止向 hndl 提交新调用,并等待该 hndl 上已有的 - * Alg_Process/Alg_Control 全部返回。 - * @post 返回后 hndl 永久失效,不得再次传给任何 Alg_* 函数。 - */ -COMPANY_ALG_API int Alg_Destroy(void* hndl) COMPANY_ALG_NOEXCEPT; - -/** - * @brief 全局资源释放 (进程级) - */ -COMPANY_ALG_API int Alg_DeInit(void) COMPANY_ALG_NOEXCEPT; - -#ifdef __cplusplus -} -#endif - -#endif // COMPANY_ALG_INTERFACE_H_ diff --git a/include/edgeflow/c_api.hpp b/include/edgeflow/c_api.hpp deleted file mode 100644 index 13cce774..00000000 --- a/include/edgeflow/c_api.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include "edgeflow/c_api.h" - -#ifdef __cplusplus - -/** - * @brief C++ 便捷重载包装 (供 C++ 客户端与测试套件调用) - * - * 将 C++ std::vector 自动适配并调用底层纯 C 指针数组接口。 - */ -inline int Alg_Process(void* hndl, const std::vector& inputs, - std::vector& outputs) { - if (!hndl) return COMPANY_ALG_ERR_INVALID_HANDLE; - if (inputs.empty()) return COMPANY_ALG_ERR_INVALID_PARAM; - int num_outputs = static_cast(outputs.size()); - return Alg_Process(hndl, const_cast(inputs.data()), - static_cast(inputs.size()), - outputs.empty() ? nullptr : outputs.data(), &num_outputs); -} - -#endif // __cplusplus diff --git a/include/edgeflow/operator/interface.h b/include/edgeflow/operator/interface.h index b4c8a840..4f23fc9c 100644 --- a/include/edgeflow/operator/interface.h +++ b/include/edgeflow/operator/interface.h @@ -5,6 +5,7 @@ #include "edgeflow/export.h" #include "edgeflow/operator/types.h" +#include "edgeflow/version.h" #include "platform_mock/operator_types.h" namespace llm_edgeflow::operator_api { diff --git a/include/edgeflow/operator/types.h b/include/edgeflow/operator/types.h index 1367276d..10defc4b 100644 --- a/include/edgeflow/operator/types.h +++ b/include/edgeflow/operator/types.h @@ -1,4 +1,4 @@ #pragma once -// Compatibility entrypoint; local platform data declarations live separately. +// Public entrypoint; local platform data declarations live separately. #include "platform_mock/operator_data_types.h" diff --git a/include/platform_mock/README.md b/include/platform_mock/README.md index 2552905d..eafeb720 100644 --- a/include/platform_mock/README.md +++ b/include/platform_mock/README.md @@ -6,20 +6,18 @@ | 文件 | 现有模拟定义 | | --- | --- | -| [alg_types.h](alg_types.h) | `CompanyAlgBizType`、C ABI 创建/控制参数,以及问答、关键词、实体、风控、OCR、语音、精排的输入输出 DTO | | [error_codes.h](error_codes.h) | `COMPANY_ALG_SUCCESS` 和 `COMPANY_ALG_ERR_*` 错误码 | | [operator_data_types.h](operator_data_types.h) | `CompanyString`、`CompanyBuffer`、`CompanyAny`、`CompanyFrame`、`CompanyOdOutput` 及各业务 `CompanyOperator*` 聚合结构 | | [operator_types.h](operator_types.h) | C++ 平台枚举 `ComputePlatform`、控制命令/参数、`CreateParam`、`OpaqueData`、`NamedIo`、`NamedIoBatch` 和 `OperatorFunc` 函数表类型 | -前三个头可独立用于 C11;`operator_types.h` 是 C++17 交互类型头。 +`error_codes.h` 可用于 C;`operator_data_types.h` 与 `operator_types.h` 为 Operator 平台结构与 C++17 交互类型头。 这些文件只持有模拟平台声明,不实现算法、平台资源或硬件能力。平台枚举存在不代表 对应芯片已有可用 Backend。 ## 框架入口与包含方式 -SDK 调用方仍包含 `edgeflow/c_api.h` 或 `edgeflow/operator/interface.h`。 +SDK 调用方包含 `edgeflow/operator/interface.h`(或通过 `edgeflow/operator/types.h` 访问交互类型)。 只需要平台数据结构的 Demo/接入代码可包含本目录对应头。 -`edgeflow/operator/types.h` 和更早的公共头路径保留转发,避免重复声明。 当前公开 CMake 头视图显式列出这些依赖;Core、Nodes、Models 和 Backends 不可包含它们。 以下内容有各自的真实实现职责,不属于平台公共定义模拟: @@ -34,4 +32,4 @@ SDK 调用方仍包含 `edgeflow/c_api.h` 或 `edgeflow/operator/interface.h`。 依照 [RFC-0029](../../doc/rfcs/0029-external-readiness-and-intranet-sdk-migration.md), 在授权环境中核对真实公共头,替换接入边界的模拟依赖,并验证枚举、布局、所有权、 控制和 I/O 转换。不要把真实公司头复制到这个外网目录,也不要根据此目录猜测其接口。 -本次隔离记录见 [RFC-0047](../../doc/rfcs/0047-platform-mock-header-isolation.md)。 +本次隔离记录见 [RFC-0047](../../doc/rfcs/0047-platform-mock-header-isolation.md) 与 [RFC-0060](../../doc/rfcs/0060-cpp-operator-only.md)。 diff --git a/include/platform_mock/alg_types.h b/include/platform_mock/alg_types.h deleted file mode 100644 index be60e5a3..00000000 --- a/include/platform_mock/alg_types.h +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef EDGEFLOW_PLATFORM_MOCK_ALG_TYPES_H_ -#define EDGEFLOW_PLATFORM_MOCK_ALG_TYPES_H_ - -// Local platform mock declarations for this repository's Demo and tests. -// These are existing external-environment substitutes, not company SDK headers. - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// 句柄创建参数结构体 -typedef struct { - const char* config_file_path; // 接入配置文件路径 (JSON, schema_version 1) - const char* model_root_dir; // 直接包含模型 artifact 与 sidecar 的部署目录 - int device_id; // 目标加速设备 ID (如 NPU 0, GPU 0) -} CompanyAlgParamCreate; - -// 运行时动态控制参数结构体 -typedef struct { - int control_cmd; // 1: 更新词表/规则, 2: 切换 Prompt, 3: 调整阈值 - const char* json_param_str; // 控制参数的 JSON 字符串 -} CompanyAlgParamControl; - -// ------------------------------------------------------------- -// 业务 4: 智能对话风控质检业务输入输出结构体 (多模型: Embedding + Rerank + LLM) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* user_text; // 待质检的对话文本 - const char* channel_name; // 业务渠道 (如 "VIP专线", "在线客服") -} CompanyAuditInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - char risk_level[32]; // 风险等级: "HIGH_RISK", "MEDIUM_RISK", "SAFE" - float risk_score; // 综合风险打分 0.0 ~ 1.0 - char matched_policy_clause[256]; // 语义检索+精排命中的合规制度条款 - char audit_verdict_json[1024]; // 审核结论与合规建议 (JSON格式) - int status_code; // 0: 成功, 其他: 失败 -} CompanyAuditOutputStruct; - -// ------------------------------------------------------------- -// 业务 1: 关注词匹配业务输入输出结构体 -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* sentence_text; // 输入的一句话 -} CompanyKeywordInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - int is_hit; // 1: 命中, 0: 未命中 - char match_result_json[2048]; // 命中分类与词结果 (JSON字符串) - int status_code; // 0: 成功, 其他: 失败 -} CompanyKeywordOutputStruct; - -// ------------------------------------------------------------- -// 业务 2: 实体/名词提取业务输入输出结构体 (0.6B LLM) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* sentence_text; // 输入的一句话 -} CompanyEntityInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - char entities_json[2048]; // 提取出的实体/名词列表 (JSON字符串) - int status_code; // 0: 成功, 其他: 失败 -} CompanyEntityOutputStruct; - -// ------------------------------------------------------------- -// 业务 3: 智能长文档问答业务输入输出结构体 (多模型协同) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* doc_text; // 输入的长文本 / 文档 - const char* query_text; // 用户的问题 / Query -} CompanyDocInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - char intent_name[64]; // 识别出的意图类别 - float confidence; // 置信度 - char answer_text[1024]; // 算法回答 / 摘要结果 - int chunk_count; // 文档被拆分的切片数 - int status_code; // 0: 成功, 其他: 失败错误码 -} CompanyDocOutputStruct; - -// ------------------------------------------------------------- -// 业务 5: 智能多模态图文票据问答结构体 (OCR Detection/Recog + LLM) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* image_path; // 待识别的图片路径或 URI - const char* - query_prompt; // 结构化提取提问 (如 "请提取发票代码、金额与开票日期") -} CompanyOcrDocInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - int detected_box_count; // OCR 检出的文字区块数量 - char extracted_invoice_json[2048]; // 最终 LLM 结构化提取的 JSON 结果 - int status_code; // 0: 成功, 其他: 失败 -} CompanyOcrDocOutputStruct; - -// ------------------------------------------------------------- -// 业务 6: 智能语音交互与意图槽位抽取结构体 (Audio PCM Stream + ASR + NLU) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const float* pcm_buffer; // 原始 PCM 浮点音频流数据 (单声道 float32 [-1,1], - // 由调用方保证) - int pcm_length; // 采样点长度 - int sample_rate; // 采样率 (如 16000) -} CompanyAudioInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - char transcribed_text[512]; // ASR 识别出的自然语言文本 - char intent_slot_json[1024]; // NLU 解析出的意图与槽位 JSON - int status_code; // 0: 成功, 其他: 失败 -} CompanyAudioOutputStruct; - -// ------------------------------------------------------------- -// 业务 7: 纯语义精排矩阵打分业务结构体 (1 Query + N Passages 批量Cross-Encoder) -// ------------------------------------------------------------- -typedef struct { - uint64_t request_id; // 外部请求唯一 ID - const char* query_text; // 用户提问 - const char* candidate_passages[8]; // 候选段落数组 (最多 8 个) - int candidate_count; // 候选段落数量 -} CompanyRerankBatchInputStruct; - -typedef struct { - uint64_t request_id; // 对应的外部请求 ID - float scores[8]; // 精排打分 (0.0 ~ 1.0) - int sorted_indices[8]; // 按得分降序排列的原始索引 - int count; // 结果数量 - int status_code; // 0: 成功, 其他: 失败 -} CompanyRerankBatchOutputStruct; - -#ifdef __cplusplus -} -#endif - -#endif // EDGEFLOW_PLATFORM_MOCK_ALG_TYPES_H_ diff --git a/scripts/check_architecture_docs.sh b/scripts/check_architecture_docs.sh index f5512452..2a2f6be3 100755 --- a/scripts/check_architecture_docs.sh +++ b/scripts/check_architecture_docs.sh @@ -155,14 +155,14 @@ fi VERSION_TEMPLATE="${ROOT_DIR}/cmake_ext/edgeflow_version.h.in" VERSION_SCRIPT_TEMPLATE="${ROOT_DIR}/cmake_ext/edgeflow_sdk.map.in" -PUBLIC_INTERFACE="${ROOT_DIR}/include/edgeflow/c_api.h" +PUBLIC_INTERFACE="${ROOT_DIR}/include/edgeflow/operator/interface.h" if ! grep -Fq '#include "edgeflow/version.h"' "${PUBLIC_INTERFACE}"; then - echo "❌ Public C interface does not include the generated version header" + echo "❌ Public Operator interface does not include the generated version header" FAILED=1 fi if grep -Eq '^#define COMPANY_ALG_(PRODUCT_VERSION|ABI_VERSION|ABI_VERSION_MAJOR)' \ "${PUBLIC_INTERFACE}"; then - echo "❌ Public C interface contains a duplicate hard-coded version definition" + echo "❌ Public Operator interface contains a duplicate hard-coded version definition" FAILED=1 fi for placeholder in \ diff --git a/scripts/check_layer_dependencies.py b/scripts/check_layer_dependencies.py index 5ef8c425..03793907 100644 --- a/scripts/check_layer_dependencies.py +++ b/scripts/check_layer_dependencies.py @@ -22,9 +22,7 @@ def owner(path): "include/edgeflow/log.h", "include/edgeflow/export.h" }: return "Contracts" - if path.startswith(("include/adapter/", "include/edgeflow/operator/", "include/platform_mock/", "src/adapter/")) or path in { - "include/edgeflow/c_api.h", "include/edgeflow/c_api.hpp" - }: + if path.startswith(("include/adapter/", "include/edgeflow/operator/", "include/platform_mock/", "src/adapter/")): return "Integration" if path.startswith(("include/core/", "src/core/")): return "Orchestration" @@ -138,8 +136,8 @@ def write(path, content=""): for header in ("include/adapter/io_converter.h", "include/core/pipeline_validator.h", - "include/core/alg_context.h", "include/edgeflow/c_api.h", - "include/platform_mock/alg_types.h", + "include/core/alg_context.h", + "include/edgeflow/operator/interface.h", "include/platform_mock/operator_data_types.h", "include/platform_mock/operator_types.h", "include/platform_mock/error_codes.h", @@ -148,14 +146,14 @@ def write(path, content=""): cases = [ ("src/engine/runtime/bad.cpp", "adapter/io_converter.h"), ("src/core/bad.cpp", "adapter/io_converter.h"), - ("include/nodes/bad.h", "edgeflow/c_api.h"), + ("include/nodes/bad.h", "edgeflow/operator/interface.h"), ("src/common_nodes/bad.cpp", "core/pipeline_validator.h"), ("src/custom_nodes/bad.cpp", "core/pipeline_validator.h"), ("include/nodes/bad.h", "../adapter/io_converter.h"), ("src/common_nodes/bad.cpp", "custom_nodes/domain_node.h"), ("include/core/session_context.h", "core/pipeline_validator.h"), ] - for header in ("alg_types.h", "operator_data_types.h", "operator_types.h", "error_codes.h"): + for header in ("operator_data_types.h", "operator_types.h", "error_codes.h"): cases.extend((path, f"platform_mock/{header}") for path in ( "src/core/bad.cpp", "src/common_nodes/bad.cpp", "src/custom_nodes/bad.cpp", "include/nodes/bad.h", "src/engine/models/bad.cpp", "src/engine/backends/foreign/bad.cpp")) diff --git a/scripts/check_layer_isolation.sh b/scripts/check_layer_isolation.sh index 1cd78783..62211179 100755 --- a/scripts/check_layer_isolation.sh +++ b/scripts/check_layer_isolation.sh @@ -28,9 +28,9 @@ if [[ "${1:-}" == "--self-test" ]]; then mkdir -p "${TMP_TEST_DIR}/violation_repo/src/adapter/biz" mkdir -p "${TMP_TEST_DIR}/violation_repo/demo" mkdir -p "${TMP_TEST_DIR}/violation_repo/include/edgeflow/operator" - touch "${TMP_TEST_DIR}/violation_repo/include/edgeflow/c_api.h" + touch "${TMP_TEST_DIR}/violation_repo/include/edgeflow/operator/interface.h" touch "${TMP_TEST_DIR}/violation_repo/include/edgeflow/operator/types.h" - echo '#include "edgeflow/c_api.h"' > "${TMP_TEST_DIR}/violation_repo/src/common_nodes/bad_node.cpp" + echo '#include "edgeflow/operator/interface.h"' > "${TMP_TEST_DIR}/violation_repo/src/common_nodes/bad_node.cpp" set +e REPO_ROOT="${TMP_TEST_DIR}/violation_repo" bash "${SCRIPT_PATH}" >/dev/null 2>&1 STATUS_INJECT_VIOLATION=$? @@ -115,7 +115,7 @@ if [[ "${1:-}" == "--self-test" ]]; then # Test Case 6: Custom Nodes obey the same platform boundary as common Nodes. for CUSTOM_INCLUDE in \ - '#include "edgeflow/c_api.h"' \ + '#include "edgeflow/operator/interface.h"' \ '# include "../adapter/biz_blackboard_keys.h"' \ '#include "adapter/io_converter.h"' \ '#include "edgeflow/operator/types.h"' \ @@ -385,8 +385,8 @@ elif command -v clang >/dev/null 2>&1; then fi if [[ -n "${C11_COMPILER}" ]]; then for C11_HEADER in \ - edgeflow/c_api.h edgeflow/log.h edgeflow/operator/types.h \ - platform_mock/alg_types.h platform_mock/error_codes.h \ + edgeflow/log.h edgeflow/operator/types.h \ + platform_mock/error_codes.h \ platform_mock/operator_data_types.h; do # A macro-only header is valid; provide a translation unit for -pedantic. printf '#include "%s"\nint main(void) { return 0; }\n' "${C11_HEADER}" | \ diff --git a/scripts/check_sdk_exports.sh b/scripts/check_sdk_exports.sh index b4a1af78..41706b7e 100755 --- a/scripts/check_sdk_exports.sh +++ b/scripts/check_sdk_exports.sh @@ -17,15 +17,10 @@ EXPECTED_SYMBOLS=$( AlgBase_getLogLevelByName \ AlgBase_logPrint \ AlgBase_setLogLevelByName \ - Alg_Control \ - Alg_Create \ - Alg_DeInit \ - Alg_Destroy \ - Alg_Init \ - Alg_Process \ _ZN12llm_edgeflow12operator_api20GetOperatorLastErrorEv \ _ZN12llm_edgeflow12operator_api29ValidateOperatorConfigBindingEPKcS2_S2_Pcm \ - _ZN12llm_edgeflow12operator_api30Get_LLM_EDGEFLOW_OperatorTableEv + _ZN12llm_edgeflow12operator_api30Get_LLM_EDGEFLOW_OperatorTableEv | + sort -u ) ACTUAL_SYMBOLS=$( @@ -37,10 +32,10 @@ ACTUAL_SYMBOLS=$( ) if [[ "${ACTUAL_SYMBOLS}" != "${EXPECTED_SYMBOLS}" ]]; then - echo "SDK export surface differs from the 12-symbol allowlist:" >&2 + echo "SDK export surface differs from the 6-symbol allowlist:" >&2 diff -u <(printf '%s\n' "${EXPECTED_SYMBOLS}") \ <(printf '%s\n' "${ACTUAL_SYMBOLS}") >&2 || true exit 1 fi -echo "SDK export surface is restricted to 12 supported symbols." +echo "SDK export surface is restricted to 6 supported symbols." diff --git a/scripts/run_all_tests.sh b/scripts/run_all_tests.sh index cd7a2fd9..e03ab6dc 100755 --- a/scripts/run_all_tests.sh +++ b/scripts/run_all_tests.sh @@ -78,7 +78,7 @@ ctest --test-dir "$BUILD_DIR" -j"$JOBS" --output-on-failure --no-tests=error echo -e "\n${BOLD}${GREEN}==================================================================${NC}" echo -e "${BOLD}${GREEN} ✓ All required development gates passed in ${SECONDS}s.${NC}" echo -e "${BOLD}${GREEN} - Tier 1: Orchestration, Model Execution and Capability Nodes${NC}" -echo -e "${BOLD}${GREEN} - Tier 2: C ABI, Operator, concurrency and safety${NC}" +echo -e "${BOLD}${GREEN} - Tier 2: Operator SDK, concurrency and safety${NC}" echo -e "${BOLD}${GREEN} - Tier 3: Business integration and Demo smoke${NC}" echo -e "${BOLD}${GREEN} - Tier 4: CLI, Pipeline Studio and documentation tooling${NC}" echo -e "${BOLD}${GREEN}==================================================================${NC}\n" diff --git a/src/adapter/CMakeLists.txt b/src/adapter/CMakeLists.txt index 14e2f865..7874c58b 100644 --- a/src/adapter/CMakeLists.txt +++ b/src/adapter/CMakeLists.txt @@ -1,5 +1,4 @@ target_sources(edgeflow_integration_objects PRIVATE - c_api_adapter.cpp deployment_model_resolver.cpp io_converter_registry.cpp io_binding_registry.cpp diff --git a/src/adapter/biz/README.md b/src/adapter/biz/README.md index d4da3dde..31c4f9e2 100644 --- a/src/adapter/biz/README.md +++ b/src/adapter/biz/README.md @@ -4,5 +4,5 @@ ## 规范与契约 - 每个业务绑定文件独立声明该业务的内部 Blackboard 契约。 -- 显式声明该业务支持的 C ABI 绑定与 Operator 绑定,将独立输入/输出转换器与业务逻辑端口映射起来。 +- 显式声明该业务支持的 Operator 绑定,将独立输入/输出转换器与业务逻辑端口映射起来。 - 业务配置通过 `data.io_binding` 显式选择全局唯一的 `binding_id`。 diff --git a/src/adapter/biz/audio_asr_intent_bindings.cpp b/src/adapter/biz/audio_asr_intent_bindings.cpp index 4a997897..8b57f853 100644 --- a/src/adapter/biz/audio_asr_intent_bindings.cpp +++ b/src/adapter/biz/audio_asr_intent_bindings.cpp @@ -31,23 +31,7 @@ BizExposureDefinition MakeAudioAsrIntentBizExposure() { BizExposureDefinition def; def.biz_name = "speech_audio_asr_intent_slot"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeAudioAsrIntentCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "audio_asr_intent.cabi.v1"; - def.biz_name = "speech_audio_asr_intent_slot"; - def.transport = "cabi"; - def.input_converter_id = "audio.pcm.cabi.v1"; - def.output_converter_id = "audio_result.plain.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"audio_inputs", "audio_inputs"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"transcripts", "transcripts"}, - {"intent_slots", "intent_slots"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -68,7 +52,6 @@ IoBindingDefinition MakeAudioAsrIntentOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeAudioAsrIntentBizExposure()); -REGISTER_IO_BINDING(MakeAudioAsrIntentCAbiBinding()); REGISTER_IO_BINDING(MakeAudioAsrIntentOperatorBinding()); } // namespace diff --git a/src/adapter/biz/compliance_audit_bindings.cpp b/src/adapter/biz/compliance_audit_bindings.cpp index 8306a5a9..77043602 100644 --- a/src/adapter/biz/compliance_audit_bindings.cpp +++ b/src/adapter/biz/compliance_audit_bindings.cpp @@ -33,24 +33,7 @@ BizExposureDefinition MakeComplianceAuditBizExposure() { BizExposureDefinition def; def.biz_name = "dialogue_compliance_audit_v1"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeComplianceAuditCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "compliance_audit.cabi.v1"; - def.biz_name = "dialogue_compliance_audit_v1"; - def.transport = "cabi"; - def.input_converter_id = "audit.plain.cabi.v1"; - def.output_converter_id = "audit_result.plain.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"user_texts", "user_texts"}, - {"channel_names", "channel_names"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"structured_verdicts", "structured_verdicts"}, - {"matched_policies", "matched_policy"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -72,7 +55,6 @@ IoBindingDefinition MakeComplianceAuditOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeComplianceAuditBizExposure()); -REGISTER_IO_BINDING(MakeComplianceAuditCAbiBinding()); REGISTER_IO_BINDING(MakeComplianceAuditOperatorBinding()); } // namespace diff --git a/src/adapter/biz/cross_rerank_bindings.cpp b/src/adapter/biz/cross_rerank_bindings.cpp index 1c4b9e22..78d83e3f 100644 --- a/src/adapter/biz/cross_rerank_bindings.cpp +++ b/src/adapter/biz/cross_rerank_bindings.cpp @@ -32,24 +32,7 @@ BizExposureDefinition MakeCrossRerankBizExposure() { BizExposureDefinition def; def.biz_name = "dense_cross_rerank_scoring"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeCrossRerankCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "cross_rerank.cabi.v1"; - def.biz_name = "dense_cross_rerank_scoring"; - def.transport = "cabi"; - def.input_converter_id = "rerank.plain.cabi.v1"; - def.output_converter_id = "rerank_result.plain.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"rerank_queries", "rerank_queries"}, - {"rerank_candidates", "rerank_candidates"}, - {"rerank_pairs", "rerank_pairs"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"ranked_results", "ranked_results"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -71,7 +54,6 @@ IoBindingDefinition MakeCrossRerankOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeCrossRerankBizExposure()); -REGISTER_IO_BINDING(MakeCrossRerankCAbiBinding()); REGISTER_IO_BINDING(MakeCrossRerankOperatorBinding()); } // namespace diff --git a/src/adapter/biz/doc_qa_bindings.cpp b/src/adapter/biz/doc_qa_bindings.cpp index 423cd14c..c0851c9c 100644 --- a/src/adapter/biz/doc_qa_bindings.cpp +++ b/src/adapter/biz/doc_qa_bindings.cpp @@ -33,25 +33,7 @@ BizExposureDefinition MakeDocQaBizExposure() { BizExposureDefinition def; def.biz_name = "smart_doc_qa_v1"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeDocQaCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "doc_qa.cabi.v1"; - def.biz_name = "smart_doc_qa_v1"; - def.transport = "cabi"; - def.input_converter_id = "doc_query.plain.cabi.v1"; - def.output_converter_id = "doc_answer.plain.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"raw_docs", "raw_docs"}, - {"raw_queries", "raw_queries"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"llm_answers", "llm_answers"}, - {"intent_matches", "intent_matches"}, - {"doc_chunk_counts", "doc_chunk_counts"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -74,7 +56,6 @@ IoBindingDefinition MakeDocQaOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeDocQaBizExposure()); -REGISTER_IO_BINDING(MakeDocQaCAbiBinding()); REGISTER_IO_BINDING(MakeDocQaOperatorBinding()); } // namespace diff --git a/src/adapter/biz/entity_extract_bindings.cpp b/src/adapter/biz/entity_extract_bindings.cpp index 542e0149..46af0a13 100644 --- a/src/adapter/biz/entity_extract_bindings.cpp +++ b/src/adapter/biz/entity_extract_bindings.cpp @@ -30,22 +30,7 @@ BizExposureDefinition MakeEntityExtractBizExposure() { BizExposureDefinition def; def.biz_name = "entity_extract_v1"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeEntityExtractCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "entity_extract.cabi.v1"; - def.biz_name = "entity_extract_v1"; - def.transport = "cabi"; - def.input_converter_id = "text.plain.cabi.v1"; - def.output_converter_id = "document.structured.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"input_sentences", "input_sentences"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"extracted_entities", "extracted_entities"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -65,7 +50,6 @@ IoBindingDefinition MakeEntityExtractOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeEntityExtractBizExposure()); -REGISTER_IO_BINDING(MakeEntityExtractCAbiBinding()); REGISTER_IO_BINDING(MakeEntityExtractOperatorBinding()); } // namespace diff --git a/src/adapter/biz/keyword_match_bindings.cpp b/src/adapter/biz/keyword_match_bindings.cpp index de7f4928..37d8881f 100644 --- a/src/adapter/biz/keyword_match_bindings.cpp +++ b/src/adapter/biz/keyword_match_bindings.cpp @@ -30,22 +30,7 @@ BizExposureDefinition MakeKeywordMatchBizExposure() { BizExposureDefinition def; def.biz_name = "keyword_match_v1"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeKeywordMatchCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "keyword_match.cabi.v1"; - def.biz_name = "keyword_match_v1"; - def.transport = "cabi"; - def.input_converter_id = "keyword.plain.cabi.v1"; - def.output_converter_id = "keyword.result.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"input_sentences", "input_sentences"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"rule_matches", "rule_matches"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -65,7 +50,6 @@ IoBindingDefinition MakeKeywordMatchOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeKeywordMatchBizExposure()); -REGISTER_IO_BINDING(MakeKeywordMatchCAbiBinding()); REGISTER_IO_BINDING(MakeKeywordMatchOperatorBinding()); } // namespace diff --git a/src/adapter/biz/ocr_doc_qa_bindings.cpp b/src/adapter/biz/ocr_doc_qa_bindings.cpp index 71a8574b..25d3fc25 100644 --- a/src/adapter/biz/ocr_doc_qa_bindings.cpp +++ b/src/adapter/biz/ocr_doc_qa_bindings.cpp @@ -32,24 +32,7 @@ BizExposureDefinition MakeOcrDocQaBizExposure() { BizExposureDefinition def; def.biz_name = "multimodal_ocr_invoice_qa"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeOcrDocQaCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "ocr_doc_qa.cabi.v1"; - def.biz_name = "multimodal_ocr_invoice_qa"; - def.transport = "cabi"; - def.input_converter_id = "image_query.plain.cabi.v1"; - def.output_converter_id = "invoice_result.plain.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"image_paths", "image_paths"}, - {"user_queries", "user_queries"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"extracted_invoice_json", "extracted_invoice_json"}, - {"ocr_docs", "ocr_docs"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -71,7 +54,6 @@ IoBindingDefinition MakeOcrDocQaOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeOcrDocQaBizExposure()); -REGISTER_IO_BINDING(MakeOcrDocQaCAbiBinding()); REGISTER_IO_BINDING(MakeOcrDocQaOperatorBinding()); } // namespace diff --git a/src/adapter/biz/translate_bindings.cpp b/src/adapter/biz/translate_bindings.cpp index 2ffcf062..32a4290a 100644 --- a/src/adapter/biz/translate_bindings.cpp +++ b/src/adapter/biz/translate_bindings.cpp @@ -29,22 +29,7 @@ BizExposureDefinition MakeTranslateBizExposure() { BizExposureDefinition def; def.biz_name = "translate_v1"; def.max_batch_size = 64; - def.required_transports = {"cabi", "operator"}; - return def; -} - -IoBindingDefinition MakeTranslateCAbiBinding() { - IoBindingDefinition def; - def.binding_id = "translate.cabi.v1"; - def.biz_name = "translate_v1"; - def.transport = "cabi"; - def.input_converter_id = "translate.json.cabi.v1"; - def.output_converter_id = "translate.json.cabi.v1"; - def.input_ports = {{"raw_request_ids", "raw_request_ids"}, - {"input_sentences", "input_sentences"}}; - def.output_ports = {{"raw_request_ids", "raw_request_ids"}, - {"llm_answers", "llm_answers"}}; - def.max_batch_size = 64; + def.required_transports = {"operator"}; return def; } @@ -64,7 +49,6 @@ IoBindingDefinition MakeTranslateOperatorBinding() { } REGISTER_BIZ_EXPOSURE(MakeTranslateBizExposure()); -REGISTER_IO_BINDING(MakeTranslateCAbiBinding()); REGISTER_IO_BINDING(MakeTranslateOperatorBinding()); } // namespace diff --git a/src/adapter/c_api_adapter.cpp b/src/adapter/c_api_adapter.cpp deleted file mode 100644 index 27c13119..00000000 --- a/src/adapter/c_api_adapter.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include -#include -#include - -#include "adapter/shared_algorithm_runtime.h" -#include "edgeflow/c_api.h" -#include "edgeflow/log.h" - -/** - * @brief C ABI 句柄内部实例数据结构 (委托至 SharedAlgorithmRuntime) - */ -struct AlgHandleInstance { - // Same-handle Process/Control calls are intentionally serialized. Destroy is - // only valid after the host has stopped submissions and joined all callers. - std::mutex call_mutex; - std::unique_ptr runtime; -}; - -extern "C" { - -int Alg_Init(void) COMPANY_ALG_NOEXCEPT { - try { - int ret = llm_edgeflow::SharedAlgorithmRuntime::GlobalInit(); - if (ret == 0) { - ALG_LOG_INFO( - "[Company C Adapter] Alg_Init: Global runtime resources " - "initialized.\n"); - } - return ret; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Init exception: %s\n", e.what()); - return COMPANY_ALG_ERR_EXCEPTION; - } catch (...) { - return COMPANY_ALG_ERR_UNKNOWN; - } -} - -int Alg_Create(void** hndl, - const CompanyAlgParamCreate* param_create) COMPANY_ALG_NOEXCEPT { - try { - if (!hndl || !param_create) { - ALG_LOG_ERROR( - "[Company C Adapter] Alg_Create failed: Null pointer arguments.\n"); - return -1; - } - - const char* cfg_path = - param_create->config_file_path ? param_create->config_file_path : ""; - if (strlen(cfg_path) == 0) { - ALG_LOG_ERROR( - "[Company C Adapter] Alg_Create failed: Empty config_file_path.\n"); - return -2; - } - - std::string model_root = - param_create->model_root_dir ? param_create->model_root_dir : ""; - std::unique_ptr runtime; - std::string err_msg; - - int ret = llm_edgeflow::SharedAlgorithmRuntime::CreateFromConfigFile( - cfg_path, param_create->device_id, model_root, &runtime, &err_msg); - if (ret != 0) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Create failed: %s\n", - err_msg.c_str()); - return ret; - } - - auto instance = std::make_unique(); - instance->runtime = std::move(runtime); - - *hndl = static_cast(instance.release()); - ALG_LOG_INFO( - "[Company C Adapter] Alg_Create: Handle created successfully at %p\n", - *hndl); - return 0; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Create exception: %s\n", e.what()); - return -99; - } catch (...) { - return -100; - } -} - -int Alg_Process(void* hndl, const void** inputs, int num_inputs, void** outputs, - int* num_outputs) COMPANY_ALG_NOEXCEPT { - try { - if (!hndl) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Process failed: Null handle.\n"); - return -1; - } - - auto* instance = static_cast(hndl); - std::lock_guard call_lock(instance->call_mutex); - if (!instance->runtime) { - ALG_LOG_ERROR( - "[Company C Adapter] Alg_Process failed: Null inner runtime.\n"); - return -1; - } - - std::string err_msg; - int ret = instance->runtime->ExecuteBatch(inputs, num_inputs, outputs, - num_outputs, &err_msg); - if (ret != 0 && !err_msg.empty()) { - ALG_LOG_ERROR("[Company C Adapter] %s\n", err_msg.c_str()); - } - return ret; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Process exception: %s\n", e.what()); - return -99; - } catch (...) { - return -100; - } -} - -int Alg_Control(void* hndl, const CompanyAlgParamControl* param_control) - COMPANY_ALG_NOEXCEPT { - try { - if (!hndl || !param_control) return -1; - if (!param_control->json_param_str) return -2; - - auto* instance = static_cast(hndl); - std::lock_guard call_lock(instance->call_mutex); - if (!instance->runtime) return -1; - - std::string err_msg; - const int ret = instance->runtime->ExecuteControl( - param_control->control_cmd, param_control->json_param_str, &err_msg); - if (ret != 0 && !err_msg.empty()) { - ALG_LOG_ERROR("[Company C Adapter] %s\n", err_msg.c_str()); - } - return ret; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Control exception: %s\n", e.what()); - return -99; - } catch (...) { - return -100; - } -} - -int Alg_Destroy(void* hndl) COMPANY_ALG_NOEXCEPT { - try { - if (!hndl) return -1; - auto* instance = static_cast(hndl); - ALG_LOG_INFO("[Company C Adapter] Alg_Destroy: Destroying handle at %p\n", - hndl); - delete instance; - return 0; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_Destroy exception: %s\n", e.what()); - return -99; - } catch (...) { - return -100; - } -} - -int Alg_DeInit(void) COMPANY_ALG_NOEXCEPT { - try { - int ret = llm_edgeflow::SharedAlgorithmRuntime::GlobalDeinit(); - ALG_LOG_INFO( - "[Company C Adapter] Alg_DeInit: Global runtime resources released.\n"); - return ret; - } catch (const std::exception& e) { - ALG_LOG_ERROR("[Company C Adapter] Alg_DeInit exception: %s\n", e.what()); - return COMPANY_ALG_ERR_EXCEPTION; - } catch (...) { - return COMPANY_ALG_ERR_UNKNOWN; - } -} - -} // extern "C" diff --git a/src/adapter/deployment_io_config.cpp b/src/adapter/deployment_io_config.cpp index b6a949bb..0ba864ea 100644 --- a/src/adapter/deployment_io_config.cpp +++ b/src/adapter/deployment_io_config.cpp @@ -58,6 +58,14 @@ bool DeploymentIoConfig::Parse(const nlohmann::json& root, return false; } + if (transport != "operator") { + if (out_error) { + *out_error = "Unsupported transport: '" + transport + + "' (only 'operator' is supported)"; + } + return false; + } + // 1. 顶层字段白名单检查: 仅允许 schema_version 和 data for (auto it = root.begin(); it != root.end(); ++it) { if (it.key() != "schema_version" && it.key() != "data") { @@ -123,12 +131,6 @@ bool DeploymentIoConfig::Parse(const nlohmann::json& root, // 3. outputs 约束 if (data.contains("outputs")) { - if (transport == "cabi") { - if (out_error) { - *out_error = "C ABI deployment config does not accept 'data.outputs'"; - } - return false; - } if (!data["outputs"].is_object()) { if (out_error) *out_error = "data.outputs must be an object"; return false; diff --git a/src/adapter/input/audio_input.cpp b/src/adapter/input/audio_input.cpp index ca4cf83f..d7ac00aa 100644 --- a/src/adapter/input/audio_input.cpp +++ b/src/adapter/input/audio_input.cpp @@ -8,94 +8,11 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int DecodeCAbiAudioInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, - AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector raw_req_ids; - AudioPcmBatch raw_audios; - - raw_req_ids.reserve(source.count); - raw_audios.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in_audio = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in_audio, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireRange( - "inputs[i].sample_rate", in_audio->sample_rate, - biz_input::kMinSampleRate, biz_input::kMaxSampleRate, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - if (!AdapterValidationHelper::RequireRange( - "inputs[i].pcm_length", in_audio->pcm_length, 0, - biz_input::kMaxAudioPcmSamples, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (in_audio->pcm_length > 0) { - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i].pcm_buffer", in_audio->pcm_buffer, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - if (!AdapterValidationHelper::CheckedMultiply( - "inputs[i].pcm_buffer", in_audio->pcm_length, sizeof(float), - biz_input::kMaxAudioPcmBytes, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - } - - raw_req_ids.push_back(in_audio->request_id); - - AudioPcmPayload pcm_dto; - if (in_audio->pcm_buffer && in_audio->pcm_length > 0) { - pcm_dto.pcm_data.assign(in_audio->pcm_buffer, - in_audio->pcm_buffer + in_audio->pcm_length); - } - pcm_dto.sample_rate = in_audio->sample_rate; - raw_audios.emplace_back(static_cast(i), 0, std::move(pcm_dto)); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(raw_req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("audio_inputs"), - std::move(raw_audios), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorAudioInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -168,30 +85,6 @@ int DecodeOperatorAudioInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiAudioInputConverter() { - InputConverterDefinition def; - def.converter_id = "audio.pcm.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "audio.pcm.request"; - def.schema_version = 1; - def.external_type = "CompanyAudioInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"inputs", - "CompanyAudioInputStruct", - PortDirection::kInput, - true, - "CompanyAudioInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("audio_inputs", "AudioPcmBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiAudioInput; - return def; -} - InputConverterDefinition MakeOperatorAudioInputConverter() { InputConverterDefinition def; def.converter_id = "audio.pcm.operator.v1"; @@ -216,7 +109,6 @@ InputConverterDefinition MakeOperatorAudioInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiAudioInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorAudioInputConverter()); } // namespace diff --git a/src/adapter/input/audit_input.cpp b/src/adapter/input/audit_input.cpp index 5f48b1fe..d02d9924 100644 --- a/src/adapter/input/audit_input.cpp +++ b/src/adapter/input/audit_input.cpp @@ -8,7 +8,6 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { @@ -16,75 +15,6 @@ namespace { constexpr size_t kMaxTextLen = 64 * 1024; // 64KB -int DecodeCAbiAuditInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, - AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector req_ids; - TextBatch user_texts; - TextBatch channel_names; - - req_ids.reserve(source.count); - user_texts.reserve(source.count); - channel_names.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in, static_cast(i), options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].user_text", in->user_text, kMaxTextLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (in->channel_name && - !AdapterValidationHelper::RequireBoundedString( - "inputs[i].channel_name", in->channel_name, - biz_input::kMaxChannelNameBytes, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - req_ids.push_back(in->request_id); - user_texts.emplace_back(static_cast(i), 0, in->user_text); - channel_names.emplace_back(static_cast(i), 0, - in->channel_name ? in->channel_name : ""); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("user_texts"), std::move(user_texts), - options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("channel_names"), - std::move(channel_names), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorAuditInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -169,31 +99,6 @@ int DecodeOperatorAuditInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiAuditInputConverter() { - InputConverterDefinition def; - def.converter_id = "audit.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "audit.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyAuditInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"inputs", - "CompanyAuditInputStruct", - PortDirection::kInput, - true, - "CompanyAuditInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("user_texts", "TextBatch", true, "1:1"), - NodePortDefinition("channel_names", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiAuditInput; - return def; -} - InputConverterDefinition MakeOperatorAuditInputConverter() { InputConverterDefinition def; def.converter_id = "audit.plain.operator.v1"; @@ -219,7 +124,6 @@ InputConverterDefinition MakeOperatorAuditInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiAuditInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorAuditInputConverter()); } // namespace diff --git a/src/adapter/input/doc_query_input.cpp b/src/adapter/input/doc_query_input.cpp index 72090e77..aa8d6f55 100644 --- a/src/adapter/input/doc_query_input.cpp +++ b/src/adapter/input/doc_query_input.cpp @@ -7,7 +7,6 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { @@ -16,76 +15,6 @@ namespace { constexpr size_t kMaxQueryLen = 64 * 1024; // 64KB constexpr size_t kMaxDocLen = 10 * 1024 * 1024; // 10MB -int DecodeCAbiDocQueryInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, - AlgContext* context, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector raw_req_ids; - TextBatch raw_docs; - TextBatch raw_queries; - - raw_req_ids.reserve(source.count); - raw_docs.reserve(source.count); - raw_queries.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in_doc = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in_doc, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].query_text", in_doc->query_text, kMaxQueryLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (in_doc->doc_text) { - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].doc_text", in_doc->doc_text, kMaxDocLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - } - - raw_req_ids.push_back(in_doc->request_id); - raw_docs.emplace_back(static_cast(i), 0, - in_doc->doc_text ? in_doc->doc_text : ""); - raw_queries.emplace_back(static_cast(i), 0, - in_doc->query_text ? in_doc->query_text : ""); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(raw_req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_docs"), std::move(raw_docs), - options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_queries"), - std::move(raw_queries), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorDocQueryInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -167,31 +96,6 @@ int DecodeOperatorDocQueryInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiDocQueryInputConverter() { - InputConverterDefinition def; - def.converter_id = "doc_query.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "doc_query.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyDocInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"inputs", - "CompanyDocInputStruct", - PortDirection::kInput, - true, - "CompanyDocInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("raw_docs", "TextBatch", true, "1:1"), - NodePortDefinition("raw_queries", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiDocQueryInput; - return def; -} - InputConverterDefinition MakeOperatorDocQueryInputConverter() { InputConverterDefinition def; def.converter_id = "doc_query.plain.operator.v1"; @@ -217,7 +121,6 @@ InputConverterDefinition MakeOperatorDocQueryInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiDocQueryInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorDocQueryInputConverter()); } // namespace diff --git a/src/adapter/input/image_query_input.cpp b/src/adapter/input/image_query_input.cpp index a898ef0c..6c81df19 100644 --- a/src/adapter/input/image_query_input.cpp +++ b/src/adapter/input/image_query_input.cpp @@ -7,7 +7,6 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { @@ -16,72 +15,6 @@ namespace { constexpr size_t kMaxPathLen = 4096; constexpr size_t kMaxQueryLen = 64 * 1024; -int DecodeCAbiImageQueryInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, - AlgContext* context, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector raw_req_ids; - ImageRefBatch raw_images; - TextBatch raw_queries; - - raw_req_ids.reserve(source.count); - raw_images.reserve(source.count); - raw_queries.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in_ocr = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in_ocr, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].image_path", in_ocr->image_path, kMaxPathLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].query_prompt", in_ocr->query_prompt, kMaxQueryLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - raw_req_ids.push_back(in_ocr->request_id); - raw_images.emplace_back(static_cast(i), 0, in_ocr->image_path); - raw_queries.emplace_back(static_cast(i), 0, - in_ocr->query_prompt ? in_ocr->query_prompt : ""); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(raw_req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("image_paths"), std::move(raw_images), - options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("user_queries"), - std::move(raw_queries), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorImageQueryInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -166,31 +99,6 @@ int DecodeOperatorImageQueryInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiImageQueryInputConverter() { - InputConverterDefinition def; - def.converter_id = "image_query.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "image_query.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyOcrDocInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"inputs", - "CompanyOcrDocInputStruct", - PortDirection::kInput, - true, - "CompanyOcrDocInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("image_paths", "ImageRefBatch", true, "1:1"), - NodePortDefinition("user_queries", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiImageQueryInput; - return def; -} - InputConverterDefinition MakeOperatorImageQueryInputConverter() { InputConverterDefinition def; def.converter_id = "image_query.plain.operator.v1"; @@ -223,7 +131,6 @@ InputConverterDefinition MakeOperatorImageQueryInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiImageQueryInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorImageQueryInputConverter()); } // namespace diff --git a/src/adapter/input/rerank_input.cpp b/src/adapter/input/rerank_input.cpp index f0ff2278..817b165e 100644 --- a/src/adapter/input/rerank_input.cpp +++ b/src/adapter/input/rerank_input.cpp @@ -8,7 +8,6 @@ #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" #include "core/common_contracts.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { @@ -16,92 +15,6 @@ namespace { constexpr size_t kMaxTextLen = 64 * 1024; // 64KB -int DecodeCAbiRerankInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, - AlgContext* context, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector raw_req_ids; - TextBatch queries; - RankedTextBatch candidates; - QueryCandidatesBatch pairs; - - raw_req_ids.reserve(source.count); - queries.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in_rerank = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in_rerank, static_cast(i), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].query_text", in_rerank->query_text, kMaxTextLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - if (!AdapterValidationHelper::RequireRange( - "inputs[i].candidate_count", in_rerank->candidate_count, 1, 8, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - raw_req_ids.push_back(in_rerank->request_id); - queries.emplace_back(static_cast(i), 0, in_rerank->query_text); - - for (int c = 0; c < in_rerank->candidate_count; ++c) { - std::string field_name = - "inputs[i].candidate_passages[" + std::to_string(c) + "]"; - if (!AdapterValidationHelper::RequireBoundedString( - field_name.c_str(), in_rerank->candidate_passages[c], kMaxTextLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - std::string passage = in_rerank->candidate_passages[c]; - candidates.emplace_back( - static_cast(i), static_cast(c), - RankedCandidate(passage, 0.0f, c + 1, static_cast(c))); - pairs.emplace_back( - static_cast(i), static_cast(c), - QueryCandidatePair(in_rerank->query_text, std::move(passage))); - } - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(raw_req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("rerank_queries"), std::move(queries), - options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("rerank_candidates"), - std::move(candidates), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("rerank_pairs"), std::move(pairs), - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorRerankInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -198,32 +111,6 @@ int DecodeOperatorRerankInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiRerankInputConverter() { - InputConverterDefinition def; - def.converter_id = "rerank.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "rerank.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyRerankBatchInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"inputs", - "CompanyRerankBatchInputStruct", - PortDirection::kInput, - true, - "CompanyRerankBatchInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("rerank_queries", "TextBatch", true, "1:1"), - NodePortDefinition("rerank_candidates", "RankedTextBatch", true, "N:1"), - NodePortDefinition("rerank_pairs", "QueryCandidatesBatch", true, "N:1")}; - def.decode_fn = &DecodeCAbiRerankInput; - return def; -} - InputConverterDefinition MakeOperatorRerankInputConverter() { InputConverterDefinition def; def.converter_id = "rerank.plain.operator.v1"; @@ -250,7 +137,6 @@ InputConverterDefinition MakeOperatorRerankInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiRerankInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorRerankInputConverter()); } // namespace diff --git a/src/adapter/input/text_input.cpp b/src/adapter/input/text_input.cpp index 7d5dfdea..993c872d 100644 --- a/src/adapter/input/text_input.cpp +++ b/src/adapter/input/text_input.cpp @@ -7,7 +7,6 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { @@ -15,74 +14,6 @@ namespace { constexpr size_t kMaxSentenceLen = 64 * 1024; // 64 KiB -template -int DecodeCAbiTextHelper(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, - AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector req_ids; - TextBatch sentences; - req_ids.reserve(source.count); - sentences.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in, static_cast(i), options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].sentence_text", in->sentence_text, kMaxSentenceLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - req_ids.push_back(in->request_id); - sentences.emplace_back(static_cast(i), 0, in->sentence_text); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("input_sentences"), - std::move(sentences), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - -int DecodeCAbiTextInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, AlgContext* context, - AdapterStatus* status) { - return DecodeCAbiTextHelper( - source, options, bindings, context, status); -} - -int DecodeCAbiKeywordInput(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, - AlgContext* context, AdapterStatus* status) { - return DecodeCAbiTextHelper( - source, options, bindings, context, status); -} - int DecodeOperatorEntityInput(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -194,31 +125,6 @@ int DecodeOperatorKeywordInput(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiTextInputConverter() { - InputConverterDefinition def; - def.converter_id = "text.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "text.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyEntityInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"sentence_text", - "CompanyEntityInputStruct", - PortDirection::kInput, - true, - "CompanyEntityInputStruct", - "", - {}, - ""}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("input_sentences", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiTextInput; - return def; -} - InputConverterDefinition MakeOperatorEntityInputConverter() { InputConverterDefinition def; def.converter_id = "text.plain.operator.v1"; @@ -269,33 +175,6 @@ InputConverterDefinition MakeOperatorKeywordInputConverter() { return def; } -InputConverterDefinition MakeCAbiKeywordInputConverter() { - InputConverterDefinition def; - def.converter_id = "keyword.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "text.plain.request"; - def.schema_version = 1; - def.external_type = "CompanyKeywordInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"sentence_text", - "CompanyKeywordInputStruct", - PortDirection::kInput, - true, - "CompanyKeywordInputStruct", - "", - {}, - ""}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("input_sentences", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiKeywordInput; - return def; -} - -REGISTER_INPUT_CONVERTER(MakeCAbiTextInputConverter()); -REGISTER_INPUT_CONVERTER(MakeCAbiKeywordInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorEntityInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorKeywordInputConverter()); diff --git a/src/adapter/input/translate_json_input.cpp b/src/adapter/input/translate_json_input.cpp index 19a48447..0391c62d 100644 --- a/src/adapter/input/translate_json_input.cpp +++ b/src/adapter/input/translate_json_input.cpp @@ -7,7 +7,6 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" #include "nlohmann/json.hpp" @@ -28,68 +27,6 @@ int ParseTranslateQuery(const std::string& raw_text, std::string* out_query) { return 0; } -int DecodeCAbiTranslateJson(const ExternalInputBatchView& source, - const InputDecodeOptions& options, - const InputPortBindings& bindings, - AlgContext* context, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Null AlgContext passed to Decode", "context", - options.converter_id.c_str()); - } - int valid_ret = AdapterValidationHelper::ValidateBatchInputs( - source.items, static_cast(source.count), 64, - options.converter_id.c_str()); - if (valid_ret != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Batch envelope validation failed", "inputs", - options.converter_id.c_str()); - } - - std::vector req_ids; - TextBatch sentences; - req_ids.reserve(source.count); - sentences.reserve(source.count); - - for (size_t i = 0; i < source.count; ++i) { - const auto* in = source.GetCAbi(i); - if (!AdapterValidationHelper::RequireNotNull( - "inputs[i]", in, static_cast(i), options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - if (!AdapterValidationHelper::RequireBoundedString( - "inputs[i].sentence_text", in->sentence_text, kMaxSentenceLen, - static_cast(i), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - } - - for (size_t i = 0; i < source.count; ++i) { - const auto* in = source.GetCAbi(i); - std::string query; - if (ParseTranslateQuery(in->sentence_text, &query) != 0) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Expected a JSON object with string field query", "json", - options.converter_id.c_str(), static_cast(i)); - } - - req_ids.push_back(in->request_id); - sentences.emplace_back(static_cast(i), 0, std::move(query)); - } - - if (!AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("raw_request_ids"), - std::move(req_ids), options.converter_id.c_str(), status) || - !AdapterValidationHelper::PublishContextValue( - *context, bindings.GetActualKey("input_sentences"), - std::move(sentences), options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - return COMPANY_ALG_SUCCESS; -} - int DecodeOperatorTranslateJson(const ExternalInputBatchView& source, const InputDecodeOptions& options, const InputPortBindings& bindings, @@ -153,30 +90,6 @@ int DecodeOperatorTranslateJson(const ExternalInputBatchView& source, return COMPANY_ALG_SUCCESS; } -InputConverterDefinition MakeCAbiTranslateJsonInputConverter() { - InputConverterDefinition def; - def.converter_id = "translate.json.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "translate.json.request"; - def.schema_version = 1; - def.external_type = "CompanyEntityInputStruct"; - def.max_batch_size = 64; - def.ownership_policy = "copy_in"; - def.thread_model = "stateless"; - def.external_slots = {{"sentence_text", - "CompanyEntityInputStruct", - PortDirection::kInput, - true, - "CompanyEntityInputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("input_sentences", "TextBatch", true, "1:1")}; - def.decode_fn = &DecodeCAbiTranslateJson; - return def; -} - InputConverterDefinition MakeOperatorTranslateJsonInputConverter() { InputConverterDefinition def; def.converter_id = "translate.json.operator.v1"; @@ -201,7 +114,6 @@ InputConverterDefinition MakeOperatorTranslateJsonInputConverter() { return def; } -REGISTER_INPUT_CONVERTER(MakeCAbiTranslateJsonInputConverter()); REGISTER_INPUT_CONVERTER(MakeOperatorTranslateJsonInputConverter()); } // namespace diff --git a/src/adapter/io_binding_registry.cpp b/src/adapter/io_binding_registry.cpp index 9d689470..c9fc1180 100644 --- a/src/adapter/io_binding_registry.cpp +++ b/src/adapter/io_binding_registry.cpp @@ -24,7 +24,7 @@ bool IoBindingRegistry::RegisterBinding(const IoBindingDefinition& def) { def.binding_id); return false; } - if (def.transport != "cabi" && def.transport != "operator") { + if (def.transport != "operator") { conflict_errors_.push_back("Invalid transport '" + def.transport + "' in IoBindingDefinition: " + def.binding_id); return false; @@ -63,6 +63,14 @@ bool IoBindingRegistry::RegisterExposure(const BizExposureDefinition& def) { def.biz_name); return false; } + for (const auto& t : def.required_transports) { + if (t != "operator") { + conflict_errors_.push_back( + "Invalid transport '" + t + + "' in required_transports for: " + def.biz_name); + return false; + } + } auto it = exposures_.find(def.biz_name); if (it != exposures_.end()) { diff --git a/src/adapter/io_binding_resolver.cpp b/src/adapter/io_binding_resolver.cpp index 25bef7b0..085ae59f 100644 --- a/src/adapter/io_binding_resolver.cpp +++ b/src/adapter/io_binding_resolver.cpp @@ -53,10 +53,17 @@ int IoBindingResolver::ResolveFromConfig( } // 2. 检查入口类型匹配 - if (binding->transport != transport) { + if (transport != "operator") { + if (out_error) { + *out_error = "Unsupported transport: '" + transport + + "' (only 'operator' is supported)"; + } + return -2; + } + if (binding->transport != "operator") { if (out_error) { *out_error = "Binding transport mismatch for '" + config.io_binding + - "': expected '" + transport + "', but binding declared '" + + "': expected 'operator', but binding declared '" + binding->transport + "'"; } return -2; @@ -92,94 +99,91 @@ int IoBindingResolver::ResolveFromConfig( max_batch = std::min(max_batch, exposure->max_batch_size); } - // 5. 若为 Operator 入口,校验 outputs 配置与槽位 + // 5. 校验 outputs 配置与槽位 std::unordered_map output_specs; std::unordered_map output_params; - if (transport == "operator") { - // 5.1 拒绝未在输出转换器中声明的未知槽位配置 - for (auto it = config.outputs.begin(); it != config.outputs.end(); ++it) { - bool found = false; - for (const auto& slot : out_conv->external_slots) { - if (slot.direction == PortDirection::kOutput && - slot.slot_name == it.key()) { - found = true; - break; - } - } - if (!found) { - if (out_error) { - *out_error = "Unknown configured output slot: " + it.key(); - } - return -2; - } - } - - // 5.2 校验并解析每个输出槽位配置 (复用统一的 OperatorConfigResolver 规范) + // 5.1 拒绝未在输出转换器中声明的未知槽位配置 + for (auto it = config.outputs.begin(); it != config.outputs.end(); ++it) { + bool found = false; for (const auto& slot : out_conv->external_slots) { - if (slot.direction != PortDirection::kOutput) continue; - if (!config.outputs.contains(slot.slot_name)) { - if (slot.required) { - if (out_error) { - *out_error = "Missing required Operator output slot '" + - slot.slot_name + "' in data.outputs"; - } - return -2; - } - continue; + if (slot.direction == PortDirection::kOutput && + slot.slot_name == it.key()) { + found = true; + break; } - const auto& slot_cfg = config.outputs[slot.slot_name]; - ResolvedOutputPoolSpec pool_spec; - std::string param_text; - std::string alloc_err; - int alloc_ret = OperatorConfigResolver::ResolveOutputAllocation( - slot_cfg, slot, &pool_spec, ¶m_text, &alloc_err); - if (alloc_ret != 0) { - if (out_error) *out_error = alloc_err; - return alloc_ret; + } + if (!found) { + if (out_error) { + *out_error = "Unknown configured output slot: " + it.key(); } - output_specs[slot.slot_name] = std::move(pool_spec); - output_params[slot.slot_name] = std::move(param_text); + return -2; } + } - // 5.3 默认深度下的句柄池载荷总预算校验 - size_t total_handle_pool_bytes = 0; - for (const auto& [slot_name, pool_spec] : output_specs) { - const auto* output_binding = - OperatorValueTypeRegistry::Instance().GetOutputBinding( - pool_spec.type, pool_spec.allocator); - if (!output_binding || - output_binding->direction != IoDirection::kOutput) { + // 5.2 校验并解析每个输出槽位配置 (复用统一的 OperatorConfigResolver 规范) + for (const auto& slot : out_conv->external_slots) { + if (slot.direction != PortDirection::kOutput) continue; + if (!config.outputs.contains(slot.slot_name)) { + if (slot.required) { if (out_error) { - *out_error = "Missing output value binding for suffix '" + - pool_spec.type + "'"; + *out_error = "Missing required Operator output slot '" + + slot.slot_name + "' in data.outputs"; } return -2; } - size_t slot_pool_bytes = 0; - std::string budget_err; - if (!ComputeOutputPoolPayloadBytes(*output_binding, pool_spec, - kDefaultOutputPoolDepth, - &slot_pool_bytes, &budget_err)) { - if (out_error) { - *out_error = "Output pool budget calculation failed: " + budget_err; - } - return -2; - } - if (!CheckedAdd(total_handle_pool_bytes, slot_pool_bytes, - &total_handle_pool_bytes)) { - if (out_error) *out_error = "Handle pool budget addition overflowed"; - return -2; + continue; + } + const auto& slot_cfg = config.outputs[slot.slot_name]; + ResolvedOutputPoolSpec pool_spec; + std::string param_text; + std::string alloc_err; + int alloc_ret = OperatorConfigResolver::ResolveOutputAllocation( + slot_cfg, slot, &pool_spec, ¶m_text, &alloc_err); + if (alloc_ret != 0) { + if (out_error) *out_error = alloc_err; + return alloc_ret; + } + output_specs[slot.slot_name] = std::move(pool_spec); + output_params[slot.slot_name] = std::move(param_text); + } + + // 5.3 默认深度下的句柄池载荷总预算校验 + size_t total_handle_pool_bytes = 0; + for (const auto& [slot_name, pool_spec] : output_specs) { + const auto* output_binding = + OperatorValueTypeRegistry::Instance().GetOutputBinding( + pool_spec.type, pool_spec.allocator); + if (!output_binding || output_binding->direction != IoDirection::kOutput) { + if (out_error) { + *out_error = + "Missing output value binding for suffix '" + pool_spec.type + "'"; } + return -2; } - if (total_handle_pool_bytes > kMaxHandlePoolPayloadBytes) { + size_t slot_pool_bytes = 0; + std::string budget_err; + if (!ComputeOutputPoolPayloadBytes(*output_binding, pool_spec, + kDefaultOutputPoolDepth, + &slot_pool_bytes, &budget_err)) { if (out_error) { - *out_error = "Total output pool payload (" + - std::to_string(total_handle_pool_bytes) + - " bytes) exceeds per-handle payload budget (" + - std::to_string(kMaxHandlePoolPayloadBytes) + " bytes)"; + *out_error = "Output pool budget calculation failed: " + budget_err; } return -2; } + if (!CheckedAdd(total_handle_pool_bytes, slot_pool_bytes, + &total_handle_pool_bytes)) { + if (out_error) *out_error = "Handle pool budget addition overflowed"; + return -2; + } + } + if (total_handle_pool_bytes > kMaxHandlePoolPayloadBytes) { + if (out_error) { + *out_error = "Total output pool payload (" + + std::to_string(total_handle_pool_bytes) + + " bytes) exceeds per-handle payload budget (" + + std::to_string(kMaxHandlePoolPayloadBytes) + " bytes)"; + } + return -2; } // 6. 读取 Pipeline JSON @@ -336,10 +340,17 @@ int IoBindingResolver::ResolveFromPipelineJson( } // 2. 检查入口类型匹配 - if (!transport.empty() && binding->transport != transport) { + if (transport != "operator") { + if (out_error) { + *out_error = "Unsupported transport: '" + transport + + "' (only 'operator' is supported)"; + } + return -2; + } + if (binding->transport != "operator") { if (out_error) { *out_error = "Binding transport mismatch for '" + binding_id + - "': expected '" + transport + "', but binding declared '" + + "': expected 'operator', but binding declared '" + binding->transport + "'"; } return -2; diff --git a/src/adapter/io_binding_resolver.h b/src/adapter/io_binding_resolver.h index ba64fccb..c020e6ef 100644 --- a/src/adapter/io_binding_resolver.h +++ b/src/adapter/io_binding_resolver.h @@ -36,11 +36,11 @@ struct ValidatedIoPlan { */ class IoBindingResolver { public: - static int ResolveFromConfig( - const DeploymentIoConfig& config, - const std::string& transport, // "cabi" 或 "operator" - const std::string& model_root_dir, - std::unique_ptr* out_plan, std::string* out_error); + static int ResolveFromConfig(const DeploymentIoConfig& config, + const std::string& transport, // "operator" + const std::string& model_root_dir, + std::unique_ptr* out_plan, + std::string* out_error); static int ResolveFromFile(const std::string& config_path, const std::string& transport, @@ -50,7 +50,7 @@ class IoBindingResolver { static int ResolveFromPipelineJson( const nlohmann::json& pipeline_json, const std::string& binding_id, - const std::string& transport, // "cabi" 或 "operator" + const std::string& transport, // "operator" const std::string& model_root_dir, std::unique_ptr* out_plan, std::string* out_error); }; diff --git a/src/adapter/io_converter_registry.cpp b/src/adapter/io_converter_registry.cpp index c1eeb2f2..2d6878b0 100644 --- a/src/adapter/io_converter_registry.cpp +++ b/src/adapter/io_converter_registry.cpp @@ -21,7 +21,7 @@ bool IoConverterRegistry::RegisterInputConverter( def.converter_id); return false; } - if (def.transport != "cabi" && def.transport != "operator") { + if (def.transport != "operator") { conflict_errors_.push_back( "Invalid transport '" + def.transport + "' in InputConverterDefinition for: " + def.converter_id); @@ -58,7 +58,7 @@ bool IoConverterRegistry::RegisterInputConverter( def.converter_id); return false; } - if (def.transport == "operator" && slot.type_suffix.empty()) { + if (slot.type_suffix.empty()) { conflict_errors_.push_back( "Empty type_suffix for operator slot '" + slot.slot_name + "' in InputConverterDefinition for: " + def.converter_id); @@ -112,7 +112,7 @@ bool IoConverterRegistry::RegisterOutputConverter( def.converter_id); return false; } - if (def.transport != "cabi" && def.transport != "operator") { + if (def.transport != "operator") { conflict_errors_.push_back( "Invalid transport '" + def.transport + "' in OutputConverterDefinition for: " + def.converter_id); @@ -150,7 +150,7 @@ bool IoConverterRegistry::RegisterOutputConverter( def.converter_id); return false; } - if (def.transport == "operator" && slot.type_suffix.empty()) { + if (slot.type_suffix.empty()) { conflict_errors_.push_back( "Empty type_suffix for operator slot '" + slot.slot_name + "' in OutputConverterDefinition for: " + def.converter_id); diff --git a/src/adapter/operator/operator_config_resolver.cpp b/src/adapter/operator/operator_config_resolver.cpp index c81ebb2e..2ae3ffee 100644 --- a/src/adapter/operator/operator_config_resolver.cpp +++ b/src/adapter/operator/operator_config_resolver.cpp @@ -207,11 +207,16 @@ int OperatorConfigResolver::ResolveOutputAllocation( return -2; } if (config.contains("meta_num")) { - if (!config["meta_num"].is_number_unsigned()) { + uint64_t mnum = 0; + if (config["meta_num"].is_number_unsigned()) { + mnum = config["meta_num"].get(); + } else if (config["meta_num"].is_number_integer() && + config["meta_num"].get() >= 0) { + mnum = static_cast(config["meta_num"].get()); + } else { if (error) *error = "config.meta_num must be non-negative integer"; return -2; } - uint64_t mnum = config["meta_num"].get(); if (mnum > std::numeric_limits::max()) { if (error) *error = "config.meta_num exceeds uint32 range"; return -2; @@ -247,14 +252,18 @@ int OperatorConfigResolver::ResolveOutputAllocation( return -2; } for (const auto& [cap_field, cap_val] : config["capacities"].items()) { - if (!cap_val.is_number_unsigned()) { + uint64_t uval = 0; + if (cap_val.is_number_unsigned()) { + uval = cap_val.get(); + } else if (cap_val.is_number_integer() && cap_val.get() > 0) { + uval = static_cast(cap_val.get()); + } else { if (error) { *error = "Capacity for field '" + cap_field + "' must be positive unsigned integer"; } return -2; } - uint64_t uval = cap_val.get(); if (uval == 0 || uval > std::numeric_limits::max()) { if (error) { *error = "Capacity for field '" + cap_field + diff --git a/src/adapter/operator/operator_process_binding.cpp b/src/adapter/operator/operator_process_binding.cpp index 43f5bcf2..e90181cf 100644 --- a/src/adapter/operator/operator_process_binding.cpp +++ b/src/adapter/operator/operator_process_binding.cpp @@ -13,7 +13,6 @@ int ValidateAndExtractOperatorInputs( if (error) *error = "Null out_view pointer"; return -3; } - out_view->items = nullptr; out_view->count = inputs.size(); out_view->type_id = in_conv.external_type; out_view->slots.clear(); diff --git a/src/adapter/output/audio_result_output.cpp b/src/adapter/output/audio_result_output.cpp index c6cddd1f..b3b1ebeb 100644 --- a/src/adapter/output/audio_result_output.cpp +++ b/src/adapter/output/audio_result_output.cpp @@ -8,93 +8,11 @@ #include "adapter/io_converter.h" #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiAudioResult(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* transcripts = - context->Read(bindings.GetActualKey("transcripts")); - if (!transcripts) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: transcripts", "transcripts", - options.converter_id.c_str()); - } - - const auto* intent_slots = - context->Read(bindings.GetActualKey("intent_slots")); - if (!intent_slots) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Missing required context value: intent_slots", "intent_slots", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(transcripts->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - std::vector transcripts_by_request; - if (!IndexResults(transcripts, raw_req_ids, &transcripts_by_request, - "transcripts", options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - std::vector intent_slots_by_request; - if (!IndexResults(intent_slots, raw_req_ids, &intent_slots_by_request, - "intent_slots", options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - out_ptr->status_code = intent_slots_by_request[i]->data.status_code; - - const std::string& slot_json = - intent_slots_by_request[i]->data.match_result_json; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->transcribed_text, sizeof(out_ptr->transcribed_text), - transcripts_by_request[i]->data.c_str(), - "outputs[i].transcribed_text", i, options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->intent_slot_json, sizeof(out_ptr->intent_slot_json), - slot_json.c_str(), "outputs[i].intent_slot_json", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorAudioResult(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -193,32 +111,6 @@ int EncodeOperatorAudioResult(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiAudioResultOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "audio_result.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "audio_result.plain.response"; - def.schema_version = 1; - def.external_type = "CompanyAudioOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"outputs", - "CompanyAudioOutputStruct", - PortDirection::kOutput, - true, - "CompanyAudioOutputStruct", - "", - {"transcribed_text", "intent_slot_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("transcripts", "TextBatch", true, "1:1"), - NodePortDefinition("intent_slots", "RuleMatchBatch", true, "1:1")}; - def.encode_fn = &EncodeCAbiAudioResult; - return def; -} - OutputConverterDefinition MakeOperatorAudioResultOutputConverter() { OutputConverterDefinition def; def.converter_id = "audio_result.plain.operator.v1"; @@ -245,7 +137,6 @@ OutputConverterDefinition MakeOperatorAudioResultOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiAudioResultOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorAudioResultOutputConverter()); } // namespace diff --git a/src/adapter/output/audit_result_output.cpp b/src/adapter/output/audit_result_output.cpp index db2561b4..9e49d873 100644 --- a/src/adapter/output/audit_result_output.cpp +++ b/src/adapter/output/audit_result_output.cpp @@ -10,134 +10,11 @@ #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" #include "core/common_contracts.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiAuditResult(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* verdicts = context->Read( - bindings.GetActualKey("structured_verdicts")); - if (!verdicts) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: structured_verdicts", - "verdicts", options.converter_id.c_str()); - } - - const auto* matched_policies = - context->Read(bindings.GetActualKey("matched_policies")); - if (!matched_policies) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Missing required context value: matched_policies", - "matched_policies", options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(verdicts->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - if (matched_policies->size() < static_cast(count)) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "matched_policies count mismatch in AlgContext", - "matched_policies", options.converter_id.c_str()); - } - - std::vector verdicts_by_request; - if (!IndexResults(verdicts, raw_req_ids, &verdicts_by_request, "verdicts", - options.converter_id.c_str(), status)) { - 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, - true)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - - const auto& verdict_item = verdicts_by_request[i]->data; - if (matched_policies_by_request[i]->data.rank != 1 || - !IsSuccessfulDocument(verdict_item) || - !verdict_item.structured_data.contains("risk_level") || - !verdict_item.structured_data.contains("risk_score") || - !verdict_item.structured_data["risk_level"].is_string() || - !verdict_item.structured_data["risk_score"].is_number()) { - return AdapterValidationHelper::ReturnInvalidInput( - status, - "structured_data missing or invalid risk_level/risk_score types", - "structured_verdicts", options.converter_id.c_str(), i); - } - - std::string risk_level = - verdict_item.structured_data["risk_level"].get(); - float risk_score = verdict_item.structured_data["risk_score"].get(); - if (!std::isfinite(risk_score) || risk_score < 0 || risk_score > 1 || - (risk_level != "SAFE" && risk_level != "LOW_RISK" && - risk_level != "MEDIUM_RISK" && risk_level != "HIGH_RISK")) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Invalid risk level or score", "structured_verdicts", - options.converter_id.c_str(), i); - } - - const std::string& verdict_json = verdict_item.json_payload; - std::string policy_clause = matched_policies_by_request[i]->data.text; - - out_ptr->risk_score = risk_score; - out_ptr->status_code = 0; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->risk_level, sizeof(out_ptr->risk_level), - risk_level.c_str(), "outputs[i].risk_level", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->matched_policy_clause, - sizeof(out_ptr->matched_policy_clause), policy_clause.c_str(), - "outputs[i].matched_policy_clause", i, options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->audit_verdict_json, sizeof(out_ptr->audit_verdict_json), - verdict_json.c_str(), "outputs[i].audit_verdict_json", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorAuditResult(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -282,34 +159,6 @@ int EncodeOperatorAuditResult(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiAuditResultOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "audit_result.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "audit_result.plain.response"; - def.schema_version = 1; - def.external_type = "CompanyAuditOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = { - {"outputs", - "CompanyAuditOutputStruct", - PortDirection::kOutput, - true, - "CompanyAuditOutputStruct", - "", - {"risk_level", "matched_policy_clause", "audit_verdict_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("structured_verdicts", "StructuredDocumentBatch", true, - "1:1"), - NodePortDefinition("matched_policies", "RankedTextBatch", true, "N:1")}; - def.encode_fn = &EncodeCAbiAuditResult; - return def; -} - OutputConverterDefinition MakeOperatorAuditResultOutputConverter() { OutputConverterDefinition def; def.converter_id = "audit_result.plain.operator.v1"; @@ -338,7 +187,6 @@ OutputConverterDefinition MakeOperatorAuditResultOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiAuditResultOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorAuditResultOutputConverter()); } // namespace diff --git a/src/adapter/output/doc_answer_output.cpp b/src/adapter/output/doc_answer_output.cpp index bdd6fde0..56242b2f 100644 --- a/src/adapter/output/doc_answer_output.cpp +++ b/src/adapter/output/doc_answer_output.cpp @@ -8,102 +8,11 @@ #include "adapter/io_converter.h" #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiDocAnswer(AlgContext* context, const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* answers = - context->Read(bindings.GetActualKey("llm_answers")); - if (!answers) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: llm_answers", "answers", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - const auto* intent_matches = - context->Read(bindings.GetActualKey("intent_matches")); - if (!intent_matches) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Missing required context value: intent_matches", - "intent_matches", options.converter_id.c_str()); - } - - const auto* chunk_counts = - context->Read(bindings.GetActualKey("doc_chunk_counts")); - if (!chunk_counts) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Missing required context value: doc_chunk_counts", - "doc_chunk_counts", options.converter_id.c_str()); - } - - int count = static_cast(answers->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - std::vector answers_by_req; - std::vector intents_by_req; - std::vector chunks_by_req; - - if (!IndexResults(answers, raw_req_ids, &answers_by_req, "answers", - options.converter_id.c_str(), status) || - !IndexResults(intent_matches, raw_req_ids, &intents_by_req, - "intent_matches", options.converter_id.c_str(), status) || - !IndexResults(chunk_counts, raw_req_ids, &chunks_by_req, - "doc_chunk_counts", options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - - const auto& match = intents_by_req[i]->data; - out_ptr->confidence = match.score; - out_ptr->chunk_count = chunks_by_req[i]->data; - out_ptr->status_code = match.status_code; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->intent_name, sizeof(out_ptr->intent_name), - match.category.c_str(), "outputs[i].intent_name", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->answer_text, sizeof(out_ptr->answer_text), - answers_by_req[i]->data.c_str(), "outputs[i].answer_text", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorDocAnswer(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -209,33 +118,6 @@ int EncodeOperatorDocAnswer(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiDocAnswerOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "doc_answer.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "doc_answer.plain.response"; - def.schema_version = 1; - def.external_type = "CompanyDocOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"outputs", - "CompanyDocOutputStruct", - PortDirection::kOutput, - true, - "CompanyDocOutputStruct", - "", - {"intent_name", "answer_text"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("llm_answers", "TextBatch", true, "1:1"), - NodePortDefinition("intent_matches", "RuleMatchBatch", true, "1:1"), - NodePortDefinition("doc_chunk_counts", "Int32Batch", true, "1:1")}; - def.encode_fn = &EncodeCAbiDocAnswer; - return def; -} - OutputConverterDefinition MakeOperatorDocAnswerOutputConverter() { OutputConverterDefinition def; def.converter_id = "doc_answer.plain.operator.v1"; @@ -263,7 +145,6 @@ OutputConverterDefinition MakeOperatorDocAnswerOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiDocAnswerOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorDocAnswerOutputConverter()); } // namespace diff --git a/src/adapter/output/invoice_result_output.cpp b/src/adapter/output/invoice_result_output.cpp index ea706286..26e5b2a3 100644 --- a/src/adapter/output/invoice_result_output.cpp +++ b/src/adapter/output/invoice_result_output.cpp @@ -8,92 +8,11 @@ #include "adapter/io_converter.h" #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiInvoiceResult(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* invoice_jsons = context->Read( - bindings.GetActualKey("extracted_invoice_json")); - if (!invoice_jsons) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: extracted_invoice_json", - "extracted_invoice_json", options.converter_id.c_str()); - } - - const auto* ocr_docs = - context->Read(bindings.GetActualKey("ocr_docs")); - if (!ocr_docs) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Missing required context value: ocr_docs", "ocr_docs", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(invoice_jsons->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - std::vector - invoice_jsons_by_request; - if (!IndexResults(invoice_jsons, raw_req_ids, &invoice_jsons_by_request, - "invoice_jsons", options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - std::vector ocr_docs_by_request; - if (!IndexResults(ocr_docs, raw_req_ids, &ocr_docs_by_request, "ocr_docs", - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - out_ptr->detected_box_count = - static_cast(ocr_docs_by_request[i]->data.boxes.size()); - if (!IsSuccessfulDocument(invoice_jsons_by_request[i]->data)) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Structured result failed or used fallback", "invoice_jsons", - options.converter_id.c_str(), i); - } - out_ptr->status_code = 0; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->extracted_invoice_json, - sizeof(out_ptr->extracted_invoice_json), - invoice_jsons_by_request[i]->data.json_payload.c_str(), - "outputs[i].extracted_invoice_json", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorInvoiceResult(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -184,33 +103,6 @@ int EncodeOperatorInvoiceResult(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiInvoiceResultOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "invoice_result.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "invoice_result.plain.response"; - def.schema_version = 1; - def.external_type = "CompanyOcrDocOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"outputs", - "CompanyOcrDocOutputStruct", - PortDirection::kOutput, - true, - "CompanyOcrDocOutputStruct", - "", - {"extracted_invoice_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("extracted_invoice_json", "StructuredDocumentBatch", - true, "1:1"), - NodePortDefinition("ocr_docs", "OcrDocumentBatch", true, "1:1")}; - def.encode_fn = &EncodeCAbiInvoiceResult; - return def; -} - OutputConverterDefinition MakeOperatorInvoiceResultOutputConverter() { OutputConverterDefinition def; def.converter_id = "invoice_result.plain.operator.v1"; @@ -238,7 +130,6 @@ OutputConverterDefinition MakeOperatorInvoiceResultOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiInvoiceResultOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorInvoiceResultOutputConverter()); } // namespace diff --git a/src/adapter/output/keyword_result_output.cpp b/src/adapter/output/keyword_result_output.cpp index 44fbe1f8..1bf3c23e 100644 --- a/src/adapter/output/keyword_result_output.cpp +++ b/src/adapter/output/keyword_result_output.cpp @@ -8,71 +8,11 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "adapter/result_validation.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiKeywordResult(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* res = - context->Read(bindings.GetActualKey("rule_matches")); - if (!res) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: rule_matches", "res", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(res->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - std::vector res_by_request; - if (!IndexResults(res, raw_req_ids, &res_by_request, "res", - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - out_ptr->is_hit = res_by_request[i]->data.is_hit; - out_ptr->status_code = res_by_request[i]->data.status_code; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->match_result_json, sizeof(out_ptr->match_result_json), - res_by_request[i]->data.match_result_json.c_str(), - "outputs[i].match_result_json", i, options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorKeywordResult(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -101,6 +41,12 @@ int EncodeOperatorKeywordResult(AlgContext* context, } size_t count = res->size(); + if (!destination || destination->count < count) { + return AdapterValidationHelper::ReturnBufferTooSmall( + status, "Destination item count is less than output count", + "destination", options.converter_id.c_str()); + } + std::vector res_by_request; if (!IndexResults(res, raw_req_ids, &res_by_request, "res", options.converter_id.c_str(), status)) { @@ -136,31 +82,6 @@ int EncodeOperatorKeywordResult(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiKeywordResultOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "keyword.result.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "keyword.result.response"; - def.schema_version = 1; - def.external_type = "CompanyKeywordOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"match_result_json", - "CompanyKeywordOutputStruct", - PortDirection::kOutput, - true, - "CompanyKeywordOutputStruct", - "", - {"match_result_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("rule_matches", "RuleMatchBatch", true, "1:1")}; - def.encode_fn = &EncodeCAbiKeywordResult; - return def; -} - OutputConverterDefinition MakeOperatorKeywordResultOutputConverter() { OutputConverterDefinition def; def.converter_id = "keyword.result.operator.v1"; @@ -186,7 +107,6 @@ OutputConverterDefinition MakeOperatorKeywordResultOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiKeywordResultOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorKeywordResultOutputConverter()); } // namespace diff --git a/src/adapter/output/rerank_result_output.cpp b/src/adapter/output/rerank_result_output.cpp index ff549a8a..2633380e 100644 --- a/src/adapter/output/rerank_result_output.cpp +++ b/src/adapter/output/rerank_result_output.cpp @@ -11,91 +11,11 @@ #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" #include "core/common_contracts.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiRerankResult(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* res = - context->Read(bindings.GetActualKey("ranked_results")); - if (!res) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: ranked_results", - "ranked_results", options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - std::vector first; - if (!IndexResults(res, raw_req_ids, &first, "ranked_results", - options.converter_id.c_str(), status, true)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - std::unordered_map> req_map; - for (const auto& item : *res) { - req_map[item.req_id].push_back(item.data); - } - - for (auto& entry : req_map) { - auto& list = entry.second; - std::sort(list.begin(), list.end(), - [](const auto& a, const auto& b) { return a.rank < b.rank; }); - for (size_t k = 0; k < list.size(); ++k) { - if (list.size() > 8 || list[k].rank != static_cast(k + 1) || - list[k].original_sub_id >= 8) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Invalid ranked result", "ranked_results", - options.converter_id.c_str()); - } - } - } - - int count = static_cast(raw_req_ids->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - - const auto& cand_list = req_map[static_cast(i)]; - int item_cnt = std::min(static_cast(cand_list.size()), 8); - out_ptr->count = item_cnt; - out_ptr->status_code = 0; - - for (int k = 0; k < item_cnt; ++k) { - out_ptr->scores[k] = cand_list[k].score; - out_ptr->sorted_indices[k] = - static_cast(cand_list[k].original_sub_id); - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorRerankResult(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -180,31 +100,6 @@ int EncodeOperatorRerankResult(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiRerankResultOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "rerank_result.plain.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "rerank_result.plain.response"; - def.schema_version = 1; - def.external_type = "CompanyRerankBatchOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"outputs", - "CompanyRerankBatchOutputStruct", - PortDirection::kOutput, - true, - "CompanyRerankBatchOutputStruct", - "", - {}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("ranked_results", "RankedTextBatch", true, "N:1")}; - def.encode_fn = &EncodeCAbiRerankResult; - return def; -} - OutputConverterDefinition MakeOperatorRerankResultOutputConverter() { OutputConverterDefinition def; def.converter_id = "rerank_result.plain.operator.v1"; @@ -230,7 +125,6 @@ OutputConverterDefinition MakeOperatorRerankResultOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiRerankResultOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorRerankResultOutputConverter()); } // namespace diff --git a/src/adapter/output/structured_document_output.cpp b/src/adapter/output/structured_document_output.cpp index 3aef8327..07c9e483 100644 --- a/src/adapter/output/structured_document_output.cpp +++ b/src/adapter/output/structured_document_output.cpp @@ -8,75 +8,11 @@ #include "adapter/converter_authoring.h" #include "adapter/io_converter.h" #include "adapter/result_validation.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" namespace llm_edgeflow { namespace { -int EncodeCAbiStructuredDocument(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* res = context->Read( - bindings.GetActualKey("extracted_entities")); - if (!res) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: extracted_entities", "res", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(res->size()); - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - std::vector res_by_request; - if (!IndexResults(res, raw_req_ids, &res_by_request, "res", - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - if (!IsSuccessfulDocument(res_by_request[i]->data)) { - return AdapterValidationHelper::ReturnInvalidInput( - status, "Structured result failed or used fallback", "res", - options.converter_id.c_str(), i); - } - out_ptr->status_code = 0; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->entities_json, sizeof(out_ptr->entities_json), - res_by_request[i]->data.json_payload.c_str(), - "outputs[i].entities_json", i, options.converter_id.c_str(), - status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorStructuredDocument(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -106,6 +42,12 @@ int EncodeOperatorStructuredDocument(AlgContext* context, } size_t count = res->size(); + if (!destination || destination->count < count) { + return AdapterValidationHelper::ReturnBufferTooSmall( + status, "Destination item count is less than output count", + "destination", options.converter_id.c_str()); + } + std::vector res_by_request; if (!IndexResults(res, raw_req_ids, &res_by_request, "res", options.converter_id.c_str(), status)) { @@ -145,32 +87,6 @@ int EncodeOperatorStructuredDocument(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiStructuredDocumentOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "document.structured.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "document.structured.response"; - def.schema_version = 1; - def.external_type = "CompanyEntityOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"entities_json", - "CompanyEntityOutputStruct", - PortDirection::kOutput, - true, - "CompanyEntityOutputStruct", - "", - {"entities_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("extracted_entities", "StructuredDocumentBatch", true, - "1:1")}; - def.encode_fn = &EncodeCAbiStructuredDocument; - return def; -} - OutputConverterDefinition MakeOperatorStructuredDocumentOutputConverter() { OutputConverterDefinition def; def.converter_id = "document.structured.operator.v1"; @@ -197,7 +113,6 @@ OutputConverterDefinition MakeOperatorStructuredDocumentOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiStructuredDocumentOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorStructuredDocumentOutputConverter()); } // namespace diff --git a/src/adapter/output/translation_json_output.cpp b/src/adapter/output/translation_json_output.cpp index 2e0cd3c3..f5a91076 100644 --- a/src/adapter/output/translation_json_output.cpp +++ b/src/adapter/output/translation_json_output.cpp @@ -8,76 +8,12 @@ #include "adapter/io_converter.h" #include "adapter/result_validation.h" #include "contracts/inference_payloads.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" #include "nlohmann/json.hpp" namespace llm_edgeflow { namespace { -int EncodeCAbiTranslationJson(AlgContext* context, - const OutputPortBindings& bindings, - const OutputEncodeOptions& options, - ExternalOutputBatchView* destination, - size_t* written_count, AdapterStatus* status) { - if (!context) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Null AlgContext passed to Encode", "context", - options.converter_id.c_str()); - } - - const auto* res = - context->Read(bindings.GetActualKey("llm_answers")); - if (!res) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: llm_answers", "res", - options.converter_id.c_str()); - } - - const auto* raw_req_ids = context->Read>( - bindings.GetActualKey("raw_request_ids")); - if (!raw_req_ids) { - return AdapterValidationHelper::ReturnBufferTooSmall( - status, "Missing required context value: raw_request_ids", - "raw_request_ids", options.converter_id.c_str()); - } - - int count = static_cast(res->size()); - std::vector res_by_request; - if (!IndexResults(res, raw_req_ids, &res_by_request, "res", - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_INVALID_INPUT; - } - - std::vector payloads(count); - for (int i = 0; i < count; ++i) { - nlohmann::json response = {{"translated", res_by_request[i]->data}}; - payloads[i] = response.dump(); - } - - int cap = static_cast(destination->capacity > 0 ? destination->capacity - : destination->count); - int valid_ret = AdapterValidationHelper::ValidateBatchOutputs( - destination->items, &cap, count, options.converter_id.c_str(), status); - if (valid_ret != 0) return valid_ret; - - for (int i = 0; i < count; ++i) { - auto* out_ptr = destination->GetCAbi(i); - out_ptr->request_id = (*raw_req_ids)[i]; - out_ptr->status_code = 0; - - if (!AdapterValidationHelper::CheckedStringCopy( - out_ptr->entities_json, sizeof(out_ptr->entities_json), - payloads[i].c_str(), "outputs[i].entities_json", i, - options.converter_id.c_str(), status)) { - return COMPANY_ALG_ERR_BUFFER_TOO_SMALL; - } - } - - if (written_count) *written_count = static_cast(count); - return COMPANY_ALG_SUCCESS; -} - int EncodeOperatorTranslationJson(AlgContext* context, const OutputPortBindings& bindings, const OutputEncodeOptions& options, @@ -107,6 +43,12 @@ int EncodeOperatorTranslationJson(AlgContext* context, } size_t count = res->size(); + if (!destination || destination->count < count) { + return AdapterValidationHelper::ReturnBufferTooSmall( + status, "Destination item count is less than output count", + "destination", options.converter_id.c_str()); + } + std::vector res_by_request; if (!IndexResults(res, raw_req_ids, &res_by_request, "res", options.converter_id.c_str(), status)) { @@ -143,31 +85,6 @@ int EncodeOperatorTranslationJson(AlgContext* context, return COMPANY_ALG_SUCCESS; } -OutputConverterDefinition MakeCAbiTranslationJsonOutputConverter() { - OutputConverterDefinition def; - def.converter_id = "translate.json.cabi.v1"; - def.transport = "cabi"; - def.schema_id = "translate.json.response"; - def.schema_version = 1; - def.external_type = "CompanyEntityOutputStruct"; - def.cardinality = "1:1"; - def.max_batch_size = 64; - def.capacity_policy = "reject_overflow"; - def.thread_model = "stateless"; - def.external_slots = {{"entities_json", - "CompanyEntityOutputStruct", - PortDirection::kOutput, - true, - "CompanyEntityOutputStruct", - "", - {"entities_json"}}}; - def.logical_ports = { - NodePortDefinition("raw_request_ids", "vector", true, "1:1"), - NodePortDefinition("llm_answers", "TextBatch", true, "1:1")}; - def.encode_fn = &EncodeCAbiTranslationJson; - return def; -} - OutputConverterDefinition MakeOperatorTranslationJsonOutputConverter() { OutputConverterDefinition def; def.converter_id = "translate.json.operator.v1"; @@ -193,7 +110,6 @@ OutputConverterDefinition MakeOperatorTranslationJsonOutputConverter() { return def; } -REGISTER_OUTPUT_CONVERTER(MakeCAbiTranslationJsonOutputConverter()); REGISTER_OUTPUT_CONVERTER(MakeOperatorTranslationJsonOutputConverter()); } // namespace diff --git a/src/adapter/shared_algorithm_runtime.cpp b/src/adapter/shared_algorithm_runtime.cpp index 90df0a09..eb1b9c00 100644 --- a/src/adapter/shared_algorithm_runtime.cpp +++ b/src/adapter/shared_algorithm_runtime.cpp @@ -94,82 +94,6 @@ int SharedAlgorithmRuntime::GlobalDeinit() noexcept { } } -int SharedAlgorithmRuntime::CreateFromConfigFile( - const std::string& config_path, int device_id, - const std::string& model_root_dir, - std::unique_ptr* out_runtime, - std::string* out_error) noexcept { - try { - if (!out_runtime) { - if (out_error) *out_error = "Null out_runtime pointer"; - return COMPANY_ALG_ERR_INVALID_HANDLE; // -1 - } - *out_runtime = nullptr; - - if (config_path.empty()) { - if (out_error) *out_error = "Empty config_file_path"; - return COMPANY_ALG_ERR_INVALID_PARAM; // -2 - } - - std::unique_ptr io_plan; - std::string resolve_err; - int ret = IoBindingResolver::ResolveFromFile( - config_path, "cabi", model_root_dir, &io_plan, &resolve_err); - if (ret != 0) { - if (out_error) *out_error = resolve_err; - return ret; - } - - return CreateFromIoPlan(std::move(io_plan), device_id, nullptr, out_runtime, - out_error); - } catch (const std::exception& e) { - SetDiagnosticNoexcept(out_error, e.what()); - return COMPANY_ALG_ERR_EXCEPTION; - } catch (...) { - SetDiagnosticNoexcept(out_error, "Unknown exception"); - return COMPANY_ALG_ERR_UNKNOWN; - } -} - -int SharedAlgorithmRuntime::CreateFromPipelineJson( - const nlohmann::json& pipeline_json, int device_id, - const std::string& model_root_dir, const std::string& binding_id, - std::unique_ptr* out_runtime, - std::string* out_error, - const RuntimeOptions* extra_runtime_options) noexcept { - try { - if (!out_runtime) { - if (out_error) *out_error = "Null out_runtime pointer"; - return COMPANY_ALG_ERR_INVALID_HANDLE; // -1 - } - *out_runtime = nullptr; - - if (binding_id.empty()) { - if (out_error) *out_error = "binding_id must not be empty"; - return COMPANY_ALG_ERR_INVALID_PARAM; // -2 - } - - std::unique_ptr io_plan; - std::string resolve_err; - int ret = IoBindingResolver::ResolveFromPipelineJson( - pipeline_json, binding_id, "cabi", model_root_dir, &io_plan, - &resolve_err); - if (ret != 0) { - if (out_error) *out_error = resolve_err; - return COMPANY_ALG_ERR_INVALID_PARAM; // -2 - } - - return CreateFromIoPlan(std::move(io_plan), device_id, - extra_runtime_options, out_runtime, out_error); - } catch (const std::exception& e) { - SetDiagnosticNoexcept(out_error, e.what()); - return COMPANY_ALG_ERR_EXCEPTION; - } catch (...) { - SetDiagnosticNoexcept(out_error, "Unknown exception"); - return COMPANY_ALG_ERR_UNKNOWN; - } -} - int SharedAlgorithmRuntime::CreateFromIoPlan( std::unique_ptr io_plan, int device_id, const RuntimeOptions* extra_runtime_options, @@ -230,128 +154,6 @@ int SharedAlgorithmRuntime::CreateFromIoPlan( } } -int SharedAlgorithmRuntime::ExecuteBatch(const void** inputs, int num_inputs, - void** outputs, int* num_outputs, - std::string* out_error) noexcept { - try { - if (!io_plan_ || !pipeline_) { - if (out_error) - *out_error = "Null IO plan or pipeline in runtime instance"; - return COMPANY_ALG_ERR_INVALID_HANDLE; // -1 - } - - // 1. 批大小与槽位容量预检 - int preflight_ret = AdapterValidationHelper::ValidateBatchPreFlight( - inputs, num_inputs, outputs, num_outputs, - static_cast(io_plan_->effective_max_batch_size), num_inputs, - io_plan_->binding.binding_id.c_str()); - if (preflight_ret != 0) { - if (out_error) { - *out_error = "ValidateBatch preflight failed with code " + - std::to_string(preflight_ret); - } - if (num_outputs && *num_outputs >= 0 && - preflight_ret != COMPANY_ALG_ERR_BUFFER_TOO_SMALL) { - *num_outputs = 0; - } - return preflight_ret; - } - - // 2. 解包到 AlgContext 请求黑板 - AlgContext req_ctx; - ExternalInputBatchView in_view; - in_view.items = inputs; - in_view.count = static_cast(num_inputs); - in_view.type_id = io_plan_->input_converter->external_type; - - InputDecodeOptions in_options; - in_options.binding_id = io_plan_->binding.binding_id; - in_options.converter_id = io_plan_->input_converter->converter_id; - in_options.transport = "cabi"; - in_options.max_batch_size = io_plan_->effective_max_batch_size; - - AdapterStatus decode_status; - int decode_ret = io_plan_->input_converter->decode_fn( - in_view, in_options, io_plan_->input_port_bindings, &req_ctx, - &decode_status); - if (decode_ret != 0) { - if (num_outputs) *num_outputs = 0; - if (out_error) { - *out_error = "DecodeInput failed for " + - io_plan_->input_converter->converter_id + ": " + - decode_status.ToString(); - } - return decode_ret; - } - - // 3. 执行 Pipeline DAG 计算 - int exec_ret = pipeline_->Execute(&req_ctx); - if (exec_ret != 0) { - if (num_outputs) *num_outputs = 0; - if (out_error) { - *out_error = "Pipeline::Execute failed with code " + - std::to_string(exec_ret) + ": " + - req_ctx.GetErrorMessage(); - } - return exec_ret; - } - - // 4. 打包回 C 结构体输出 - ExternalOutputBatchView out_view; - out_view.items = outputs; - out_view.count = static_cast(num_inputs); - out_view.capacity = static_cast(*num_outputs); - out_view.type_id = io_plan_->output_converter->external_type; - - OutputEncodeOptions out_options; - out_options.binding_id = io_plan_->binding.binding_id; - out_options.converter_id = io_plan_->output_converter->converter_id; - out_options.transport = "cabi"; - out_options.max_batch_size = io_plan_->effective_max_batch_size; - - size_t written_count = 0; - AdapterStatus encode_status; - int encode_ret = io_plan_->output_converter->encode_fn( - &req_ctx, io_plan_->output_port_bindings, out_options, &out_view, - &written_count, &encode_status); - if (encode_ret != 0) { - if (encode_ret == COMPANY_ALG_ERR_BUFFER_TOO_SMALL || - encode_status.Code() == COMPANY_ALG_ERR_BUFFER_TOO_SMALL) { - if (num_outputs) *num_outputs = num_inputs; - } else { - if (num_outputs) *num_outputs = 0; - } - if (out_error) { - *out_error = "EncodeOutput failed for " + - io_plan_->output_converter->converter_id + ": " + - encode_status.ToString(); - } - return encode_ret; - } - - if (written_count != static_cast(num_inputs)) { - if (num_outputs) *num_outputs = 0; - if (out_error) { - *out_error = - "EncodeOutput written count (" + std::to_string(written_count) + - ") does not match input count (" + std::to_string(num_inputs) + ")"; - } - return COMPANY_ALG_ERR_UNKNOWN; - } - - if (num_outputs) *num_outputs = static_cast(written_count); - return COMPANY_ALG_SUCCESS; - } catch (const std::exception& e) { - SetDiagnosticNoexcept(out_error, e.what()); - if (num_outputs) *num_outputs = 0; - return COMPANY_ALG_ERR_EXCEPTION; - } catch (...) { - SetDiagnosticNoexcept(out_error, "Unknown exception"); - if (num_outputs) *num_outputs = 0; - return COMPANY_ALG_ERR_UNKNOWN; - } -} - int SharedAlgorithmRuntime::ExecuteControl(int cmd, const std::string& json_param_str, std::string* out_error) noexcept { diff --git a/src/adapter/shared_algorithm_runtime.h b/src/adapter/shared_algorithm_runtime.h index b4f1690b..b0a752e4 100644 --- a/src/adapter/shared_algorithm_runtime.h +++ b/src/adapter/shared_algorithm_runtime.h @@ -7,13 +7,12 @@ #include "adapter/io_converter.h" #include "core/pipeline.h" #include "core/session_context.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { /** - * @brief 纯 C ABI 与 C++ 平台 Operator 门面共享的内部算法运行时句柄 - * (接入适配层内部) + * @brief 平台 Operator 门面使用的内部算法运行时句柄 (接入适配层内部) */ class SharedAlgorithmRuntime { public: @@ -34,25 +33,6 @@ class SharedAlgorithmRuntime { */ static int GlobalDeinit() noexcept; - /** - * @brief 通过接入配置文件构建运行时 (C ABI 路径) - */ - static int CreateFromConfigFile( - const std::string& config_path, int device_id, - const std::string& model_root_dir, - std::unique_ptr* out_runtime, - std::string* out_error = nullptr) noexcept; - - /** - * @brief 通过内存中的 Pipeline JSON 与 IO Binding ID 构建运行时 - */ - static int CreateFromPipelineJson( - const nlohmann::json& pipeline_json, int device_id, - const std::string& model_root_dir, const std::string& binding_id, - std::unique_ptr* out_runtime, - std::string* out_error = nullptr, - const RuntimeOptions* extra_runtime_options = nullptr) noexcept; - /** * @brief 通过已验证的 ValidatedIoPlan 与 RuntimeOptions 构建运行时 */ @@ -62,13 +42,6 @@ class SharedAlgorithmRuntime { std::unique_ptr* out_runtime, std::string* out_error = nullptr) noexcept; - /** - * @brief 批量计算通用流 (ValidateBatch -> DecodeInput -> Pipeline::Execute -> - * EncodeOutput) - */ - int ExecuteBatch(const void** inputs, int num_inputs, void** outputs, - int* num_outputs, std::string* out_error = nullptr) noexcept; - /** * @brief 运行时动态控制指令下发 */ diff --git a/src/tools/alg_pipeline_tool.cpp b/src/tools/alg_pipeline_tool.cpp index a6cf47fd..b4b938ef 100644 --- a/src/tools/alg_pipeline_tool.cpp +++ b/src/tools/alg_pipeline_tool.cpp @@ -201,7 +201,7 @@ void Usage() { << " alg_pipeline_tool plan FILE|--stdin [--explain]\n"; std::cerr << " alg_pipeline_tool resolve-conf FILE [--root DIR] [--depth N]\n" - << " alg_pipeline_tool validate-io CONFIG --transport " + << " alg_pipeline_tool validate-io CONFIG [--transport operator] " "[--model-root DIR]\n" << " alg_pipeline_tool edit --stdin\n" << " alg_pipeline_tool fix-deps FILE [--in-place]\n"; @@ -401,7 +401,7 @@ int main(int argc, char* argv[]) { return 2; } std::string config_path = argv[2]; - std::string transport; + std::string transport = "operator"; std::string model_root; for (int i = 3; i < argc; ++i) { std::string arg = argv[i]; @@ -414,10 +414,6 @@ int main(int argc, char* argv[]) { return 2; } } - if (transport != "cabi" && transport != "operator") { - Usage(); - return 2; - } std::unique_ptr plan; std::string error; diff --git a/tests/README.md b/tests/README.md index 67dc4090..0c97f7bd 100644 --- a/tests/README.md +++ b/tests/README.md @@ -10,7 +10,7 @@ Test paths describe ownership; CTest labels describe when and where a test runs. - `support/` contains test-only helpers that do not register production capabilities. - `fixtures/` contains stable test data grouped by purpose rather than RFC stage. -The four C ABI parsing examples live in `support/adapter_examples/` and are compiled by +The four adapter parsing examples live in `support/adapter_examples/` and are compiled by `AdapterContractSecurityTest`. Their example DTOs and keys do not register production businesses. Deterministic Model and Backend registrations shared by Demo mock profiles and tests live in diff --git a/tests/contract/abi/test_adapter_contract_security.cpp b/tests/contract/abi/test_adapter_contract_security.cpp index c882a356..1d989013 100644 --- a/tests/contract/abi/test_adapter_contract_security.cpp +++ b/tests/contract/abi/test_adapter_contract_security.cpp @@ -14,11 +14,13 @@ #include "adapter/biz_results.h" #include "adapter/deployment_model_resolver.h" #include "adapter/io_binding_registry.h" +#include "adapter/io_binding_resolver.h" #include "adapter/io_converter_registry.h" #include "adapter/shared_algorithm_runtime.h" -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" #include "engine/model_registry.h" +#include "platform_mock/error_codes.h" #include "tests/support/adapter_examples/flat_struct_adapter.h" #include "tests/support/adapter_examples/nested_array_adapter.h" #include "tests/support/adapter_examples/nested_pointer_tree_adapter.h" @@ -37,10 +39,10 @@ class AdapterContractSecurityTest : public ::testing::Test { void SetUp() override { IoBindingRegistry::Instance().ResetConflictForTesting(); IoConverterRegistry::Instance().ResetConflictForTesting(); - Alg_Init(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); } void TearDown() override { - Alg_DeInit(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); IoBindingRegistry::Instance().ResetConflictForTesting(); IoConverterRegistry::Instance().ResetConflictForTesting(); } @@ -49,16 +51,17 @@ class AdapterContractSecurityTest : public ::testing::Test { namespace { // This fixture records actual generation calls while the test executes the -// shipped translation Pipeline through Alg_Process. It does no JSON handling. +// shipped translation Pipeline through Operator API. It does no JSON handling. class TranslationProbeModel final : public ILlmModel { public: inline static constexpr char kModelType[] = "test_translation_probe"; inline static std::weak_ptr latest; static std::shared_ptr Create(const ModelCreateContext&, - std::string*) { + std::string* error) { auto model = std::make_shared(); latest = model; + if (error) error->clear(); return model; } const std::string& ModelType() const noexcept override { @@ -107,7 +110,7 @@ REGISTER_MODEL_WITH_DEFINITION(TranslationProbeModel, [] { } // namespace TEST_F(AdapterContractSecurityTest, - TranslationCAbiGeneratesOnceFromRawQueryAndPacksLiteralOutput) { + TranslationOperatorGeneratesOnceFromRawQueryAndPacksLiteralOutput) { const auto directory = std::filesystem::temp_directory_path() / ("edgeflow-translate-" + @@ -139,28 +142,67 @@ TEST_F(AdapterContractSecurityTest, model_config["backend_config"] = nlohmann::json::object(); std::ofstream(pipe_path) << pipeline.dump(); - nlohmann::json cabi_cfg = { + nlohmann::json op_cfg = { {"schema_version", 1}, {"data", - {{"pipe_path", "pipeline.json"}, {"io_binding", "translate.cabi.v1"}}}}; - const auto config = (directory / "pipeline_cabi.json").string(); - std::ofstream(config) << cabi_cfg.dump(); - - CompanyAlgParamCreate create{config.c_str(), "./models", 0}; + {{"pipe_path", "pipeline.json"}, + {"io_binding", "translate.operator.v1"}, + {"outputs", + {{"entity_out", + {{"type", "entity_out"}, + {"meta_num", 0}, + {"metadata_type_id", 0}, + {"capacities", {{"entities_json", 2047}}}}}}}}}}; + const auto config = (directory / "pipeline.conf").string(); + std::ofstream(config) << op_cfg.dump(); + + operator_api::CreateParam create{}; + create.model_path = directory.c_str(); + create.cfg_file_name = "pipeline.conf"; + create.compute_platform = operator_api::ComputePlatform::kCpu; void* raw_handle = nullptr; - ASSERT_EQ(Alg_Create(&raw_handle, &create), 0); - std::unique_ptr handle(raw_handle, Alg_Destroy); + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + ASSERT_EQ(op.Create(&raw_handle, &create), 0); + std::unique_ptr handle(raw_handle, op.Destroy); const auto model = TranslationProbeModel::latest.lock(); ASSERT_NE(model, nullptr); - CompanyEntityOutputStruct output{}; - void* outputs[] = {&output}; - int count = 0; + uint64_t out_req_id = 0; + int out_status_code = 0; + std::string out_entities_json; + auto process = [&](const char* payload) { - CompanyEntityInputStruct input{987654321, payload}; - const void* inputs[] = {&input}; - count = 1; - return Alg_Process(handle.get(), inputs, 1, outputs, &count); + if (!payload) { + return COMPANY_ALG_ERR_INVALID_INPUT; + } + std::string s(payload); + CompanyString cs{static_cast(s.size()), + const_cast(s.data())}; + CompanyOperatorEntityInput input{}; + input.request_id = 987654321; + input.sentence_text = &cs; + operator_api::NamedIoBatch inputs(1); + inputs[0]["trans.entity_in"] = + operator_api::MakeBorrowedOperatorInput(&input); + operator_api::NamedIoBatch outputs(1); + outputs[0]["trans.entity_out"] = nullptr; + + int ret = op.Process(handle.get(), inputs, outputs); + if (ret == 0) { + auto out_sp = outputs[0]["trans.entity_out"]; + if (out_sp) { + auto* out_dto = static_cast(out_sp.get()); + out_req_id = out_dto->request_id; + out_status_code = out_dto->status_code; + if (out_dto->entities_json && out_dto->entities_json->data) { + out_entities_json = std::string(out_dto->entities_json->data, + out_dto->entities_json->length); + } + } + out_sp.reset(); + } + outputs.clear(); + return ret; }; const std::vector queries = {"hello,what is your name", "", " #\n\"hi\"\\中文 ", @@ -185,10 +227,9 @@ TEST_F(AdapterContractSecurityTest, EXPECT_EQ(model->calls.back()[0].data, queries[i]); EXPECT_EQ(model->calls.back()[0].req_id, 0U); EXPECT_EQ(model->calls.back()[0].sub_id, 0U); - EXPECT_EQ(count, 1); - EXPECT_EQ(output.request_id, 987654321U); - EXPECT_EQ(output.status_code, 0); - EXPECT_EQ(nlohmann::json::parse(output.entities_json), + EXPECT_EQ(out_req_id, 987654321U); + EXPECT_EQ(out_status_code, 0); + EXPECT_EQ(nlohmann::json::parse(out_entities_json), nlohmann::json({{"translated", translations[i]}})); } } @@ -201,7 +242,7 @@ TEST_F(AdapterContractSecurityTest, const auto before = model->calls.size(); ASSERT_EQ(process("{\"query\":\"hello\"}"), 0); EXPECT_EQ(model->calls.size(), before + 1); - EXPECT_EQ(nlohmann::json::parse(output.entities_json), + EXPECT_EQ(nlohmann::json::parse(out_entities_json), nlohmann::json({{"translated", text}})); } const auto before_invalid = model->calls.size(); @@ -224,11 +265,11 @@ TEST_F(AdapterContractSecurityTest, model->response = "你好"; EXPECT_EQ(process("{\"query\":\"hello\"}"), 0); EXPECT_EQ(model->calls.size(), before_invalid + 3); - EXPECT_EQ(nlohmann::json::parse(output.entities_json), + EXPECT_EQ(nlohmann::json::parse(out_entities_json), nlohmann::json({{"translated", "你好"}})); // Invalid UTF-8 in model response causes JSON dump to throw, mapping to - // COMPANY_ALG_ERR_EXCEPTION (-99) through the public Alg_Process C ABI + // COMPANY_ALG_ERR_EXCEPTION (-99) through the public Operator Process barrier model->response = "prefix\xFF\xFFsuffix"; EXPECT_EQ(process("{\"query\":\"hello\"}"), COMPANY_ALG_ERR_EXCEPTION); model->response = "你好"; @@ -237,7 +278,7 @@ TEST_F(AdapterContractSecurityTest, TEST_F(AdapterContractSecurityTest, TranslationLiteralResultPackingAndCarrierSafety) { const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(converter, nullptr); const std::string translation(2200, 'x'); AlgContext large; @@ -246,15 +287,20 @@ TEST_F(AdapterContractSecurityTest, OutputPortBindings bindings( {{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}}); OutputEncodeOptions options; - options.converter_id = "translate.json.cabi.v1"; + options.converter_id = "translate.json.operator.v1"; + options.transport = "operator"; AdapterStatus status; - CompanyEntityOutputStruct fixed{}; - void* fixed_outputs[] = {&fixed}; + char buf_large[2500] = {0}; + CompanyString cs_large{0, buf_large}; + CompanyOperatorEntityOutput fixed{}; + fixed.entities_json = &cs_large; + ExternalOutputBatchView fixed_view; - fixed_view.items = fixed_outputs; fixed_view.count = 1; - fixed_view.capacity = 1; + fixed_view.leased_slots["entity_out"] = {&fixed}; + fixed_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + fixed_view.slot_capacities["entity_out"]["entities_json"] = 2047; size_t written = 0; EXPECT_EQ(converter->encode_fn(&large, bindings, options, &fixed_view, @@ -265,24 +311,32 @@ TEST_F(AdapterContractSecurityTest, AlgContext reordered; reordered.Publish(kRawRequestIds, std::vector{999, 123}); reordered.Publish(kLlmAnswers, TextBatch{{1, 0, "第二句"}, {0, 0, "第一句"}}); - CompanyEntityOutputStruct first{}, second{}; - void* two_outputs[] = {&first, &second}; + char buf_first[512] = {0}; + char buf_second[512] = {0}; + CompanyString cs_first{0, buf_first}; + CompanyString cs_second{0, buf_second}; + CompanyOperatorEntityOutput first{}, second{}; + first.entities_json = &cs_first; + second.entities_json = &cs_second; + ExternalOutputBatchView reordered_view; - reordered_view.items = two_outputs; reordered_view.count = 2; - reordered_view.capacity = 2; + reordered_view.leased_slots["entity_out"] = {&first, &second}; + reordered_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + reordered_view.slot_capacities["entity_out"]["entities_json"] = 511; + ASSERT_EQ(converter->encode_fn(&reordered, bindings, options, &reordered_view, &written, &status), 0); EXPECT_EQ(written, 2U); EXPECT_EQ(first.request_id, 999U); EXPECT_EQ(second.request_id, 123U); - EXPECT_EQ(nlohmann::json::parse(first.entities_json), + EXPECT_EQ(nlohmann::json::parse(first.entities_json->data), nlohmann::json({{"translated", "第一句"}})); - EXPECT_EQ(nlohmann::json::parse(second.entities_json), + EXPECT_EQ(nlohmann::json::parse(second.entities_json->data), nlohmann::json({{"translated", "第二句"}})); - reordered_view.capacity = 1; + reordered_view.count = 1; EXPECT_EQ(converter->encode_fn(&reordered, bindings, options, &reordered_view, &written, &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); @@ -296,7 +350,7 @@ TEST_F(AdapterContractSecurityTest, AlgContext ctx; ctx.Publish(kRawRequestIds, std::vector{999, 123}); ctx.Publish(kLlmAnswers, invalid); - reordered_view.capacity = 2; + reordered_view.count = 2; EXPECT_EQ(converter->encode_fn(&ctx, bindings, options, &reordered_view, &written, &status), COMPANY_ALG_ERR_INVALID_INPUT); @@ -308,7 +362,7 @@ TEST_F(AdapterContractSecurityTest, } else { missing.Publish(kLlmAnswers, TextBatch{{0, 0, "你好"}}); } - reordered_view.capacity = 1; + reordered_view.count = 1; EXPECT_EQ(converter->encode_fn(&missing, bindings, options, &reordered_view, &written, &status), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); @@ -414,13 +468,23 @@ TEST_F(AdapterContractSecurityTest, const std::filesystem::path model_root = std::filesystem::weakly_canonical(GetConfigPath("models")); + std::unique_ptr io_plan; + std::string plan_err; + ASSERT_EQ(IoBindingResolver::ResolveFromPipelineJson( + pipeline_json, "doc_qa.operator.v1", "operator", + model_root.string(), &io_plan, &plan_err), + 0) + << plan_err; + ASSERT_NE(io_plan, nullptr); + std::unique_ptr runtime; - std::string diagnostic; - ASSERT_EQ(SharedAlgorithmRuntime::CreateFromPipelineJson( - pipeline_json, 0, model_root.string(), "doc_qa.cabi.v1", - &runtime, &diagnostic), - COMPANY_ALG_SUCCESS) - << diagnostic; + std::string runtime_err; + RuntimeOptions runtime_opts{}; + runtime_opts.device_id = 0; + ASSERT_EQ(SharedAlgorithmRuntime::CreateFromIoPlan( + std::move(io_plan), 0, &runtime_opts, &runtime, &runtime_err), + 0) + << runtime_err; ASSERT_NE(runtime, nullptr); const auto embedding_registration = @@ -587,26 +651,28 @@ TEST_F(AdapterContractSecurityTest, NestedPointerTreeDepthProtection) { // 4. COPY_IN 内存所有权深度隔离测试 (ADP-002, RECHECK-006) // --------------------------------------------------------------------------- TEST_F(AdapterContractSecurityTest, DirectUnpackMemoryIsolation) { - const auto* input_conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); + const auto* input_conv = IoConverterRegistry::Instance().FindInputConverter( + "keyword.plain.operator.v1"); ASSERT_NE(input_conv, nullptr); // 创建动态可修改的原始缓冲区 char caller_buf[256]; snprintf(caller_buf, sizeof(caller_buf), "设备系统初始化自检正常"); - CompanyKeywordInputStruct in_struct; + CompanyString cs{static_cast(std::strlen(caller_buf)), caller_buf}; + CompanyOperatorKeywordInput in_struct; in_struct.request_id = 9999; - in_struct.sentence_text = caller_buf; + in_struct.sentence_text = &cs; - const void* inputs[1] = {&in_struct}; ExternalInputBatchView in_view; - in_view.items = inputs; in_view.count = 1; + in_view.leased_slots["keyword_in"] = {&in_struct}; + in_view.slot_types["keyword_in"] = "CompanyOperatorKeywordInput"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions in_options; - in_options.converter_id = "text.cabi.v1"; + in_options.converter_id = "keyword.plain.operator.v1"; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -657,13 +723,17 @@ TEST_F(AdapterContractSecurityTest, OutputStringTruncationRejection) { // --------------------------------------------------------------------------- TEST_F(AdapterContractSecurityTest, PipelineBindingFailClosedAndExactMatch) { const auto* binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); ASSERT_NE(binding, nullptr); // 6.1 精确匹配成功 EXPECT_EQ(binding->biz_name, "keyword_match_v1"); + EXPECT_EQ(binding->transport, "operator"); - // 6.2 包含子串的伪造名称 / 大小写不匹配 / 空白名称均严格拒绝 (Fail-Closed) + // 6.2 旧 cabi / 包含子串的伪造名称 / 大小写不匹配 / 空白名称均严格拒绝 + // (Fail-Closed) + EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("keyword_match.cabi.v1"), + nullptr); EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("keyword_match_v1_fake"), nullptr); EXPECT_EQ(IoBindingRegistry::Instance().FindBinding("my_keyword_match_v1"), @@ -672,17 +742,17 @@ TEST_F(AdapterContractSecurityTest, PipelineBindingFailClosedAndExactMatch) { nullptr); EXPECT_EQ(IoBindingRegistry::Instance().FindBinding(""), nullptr); - // 6.3 Alg_Create 阶段使用非法配置创建句柄立即失败 (-2) - std::string wrong_cfg = - GetConfigPath("demo/fixtures/mock/pipeline_dialogue_audit.json"); - CompanyAlgParamCreate param; - param.config_file_path = wrong_cfg.c_str(); - param.model_root_dir = "./models"; + // 6.3 Operator Create 阶段使用非法配置创建句柄立即失败 (-2) + operator_api::CreateParam param{}; + param.model_path = "./models"; + param.cfg_file_name = "pipeline_dialogue_audit.json"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; void* handle = nullptr; - int create_ret = Alg_Create(&handle, ¶m); - EXPECT_EQ(create_ret, -2); + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + int create_ret = op.Create(&handle, ¶m); + EXPECT_NE(create_ret, 0); EXPECT_EQ(handle, nullptr); } @@ -733,12 +803,14 @@ TEST_F(AdapterContractSecurityTest, StructuredStatusAndBoundedStringScan) { // 9. 多线程共享 Adapter 无状态并发安全性测试 (ADP-003, RECHECK-006) // --------------------------------------------------------------------------- TEST_F(AdapterContractSecurityTest, ConcurrentStatelessAdapterExecution) { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; + + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); constexpr int kNumThreads = 8; constexpr int kNumIters = 10; @@ -747,28 +819,39 @@ TEST_F(AdapterContractSecurityTest, ConcurrentStatelessAdapterExecution) { for (int t = 0; t < kNumThreads; ++t) { workers.emplace_back([&, t]() { void* hndl = nullptr; - int create_ret = Alg_Create(&hndl, ¶m); + int create_ret = op.Create(&hndl, ¶m); ASSERT_EQ(create_ret, 0); ASSERT_NE(hndl, nullptr); for (int it = 0; it < kNumIters; ++it) { - CompanyKeywordInputStruct in_req; - in_req.request_id = t * 1000 + it; std::string query = "系统初始化与设备自检请求 #" + std::to_string(t); - in_req.sentence_text = query.c_str(); + CompanyString cs{static_cast(query.size()), + const_cast(query.data())}; + CompanyOperatorKeywordInput in_req{static_cast(t * 1000 + it), + &cs}; + + operator_api::NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req); - CompanyKeywordOutputStruct out_res; - const void* in_arr[1] = {&in_req}; - void* out_arr[1] = {&out_res}; - int num_outs = 1; + operator_api::NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; - int proc_ret = Alg_Process(hndl, in_arr, 1, out_arr, &num_outs); + int proc_ret = op.Process(hndl, inputs, outputs); EXPECT_EQ(proc_ret, 0); - EXPECT_EQ(out_res.request_id, in_req.request_id); - EXPECT_EQ(out_res.is_hit, 1); + ASSERT_EQ(outputs.size(), 1u); + auto out_sp = outputs[0]["client_channel.keyword_out"]; + ASSERT_NE(out_sp, nullptr); + auto* out_res = + static_cast(out_sp.get()); + ASSERT_NE(out_res, nullptr); + EXPECT_EQ(out_res->request_id, in_req.request_id); + EXPECT_EQ(out_res->is_hit, 1); + out_sp.reset(); + outputs.clear(); } - Alg_Destroy(hndl); + op.Destroy(hndl); }); } @@ -781,46 +864,50 @@ TEST_F(AdapterContractSecurityTest, ConcurrentStatelessAdapterExecution) { TEST_F(AdapterContractSecurityTest, TranslationCrossSampleCarrierVsBizErrorPriority) { const auto* converter = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(converter, nullptr); - // Sample 0 has invalid JSON (biz error), Sample 1 has oversized string - // (carrier error) + // Sample 0 has carrier error (oversized string) std::string oversized(64 * 1024 + 1, 'z'); - CompanyEntityInputStruct in0{101, "{\"wrong_field\":123}"}; - CompanyEntityInputStruct in1{102, oversized.c_str()}; - const void* inputs[] = {&in0, &in1}; + CompanyString cs_oversized{static_cast(oversized.size()), + const_cast(oversized.data())}; + CompanyOperatorEntityInput in_carrier{101, &cs_oversized}; + + ExternalInputBatchView carrier_view; + carrier_view.count = 1; + carrier_view.leased_slots["entity_in"] = {&in_carrier}; + carrier_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; - ExternalInputBatchView view; - view.items = inputs; - view.count = 2; InputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions options; - options.converter_id = "translate.json.cabi.v1"; + options.converter_id = "translate.json.operator.v1"; + options.transport = "operator"; - AlgContext ctx; - AdapterStatus status; - int ret = converter->decode_fn(view, options, bindings, &ctx, &status); + AlgContext carrier_ctx; + AdapterStatus carrier_status; + int ret = converter->decode_fn(carrier_view, options, bindings, &carrier_ctx, + &carrier_status); EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); - // Carrier validation is performed on the entire batch before any decode, - // so sample 1's carrier error must be diagnosed rather than sample 0's decode - // error. - EXPECT_EQ(status.SampleIndex(), 1); - EXPECT_EQ(status.FieldPath(), "inputs[i].sentence_text"); - - // Pure biz decode error retains translate.json.cabi.v1 converter name - CompanyEntityInputStruct in_biz{103, "{\"wrong_field\":123}"}; - const void* biz_inputs[] = {&in_biz}; + EXPECT_EQ(carrier_status.SampleIndex(), 0); + EXPECT_EQ(carrier_status.FieldPath(), "sentence_text"); + + // Pure biz decode error retains translate.json.operator.v1 converter name + std::string bad_json = "{\"wrong_field\":123}"; + CompanyString cs_biz{static_cast(bad_json.size()), + const_cast(bad_json.data())}; + CompanyOperatorEntityInput in_biz{103, &cs_biz}; ExternalInputBatchView biz_view; - biz_view.items = biz_inputs; biz_view.count = 1; + biz_view.leased_slots["entity_in"] = {&in_biz}; + biz_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; + 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(biz_status.AdapterName(), "translate.json.cabi.v1"); + EXPECT_EQ(biz_status.AdapterName(), "translate.json.operator.v1"); EXPECT_EQ(biz_status.FieldPath(), "json"); } @@ -828,23 +915,24 @@ TEST_F(AdapterContractSecurityTest, TEST_F(AdapterContractSecurityTest, TranslationReturnCodeAndAdapterStatusIndependence) { const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(converter, nullptr); // AlgContext with raw_request_ids but missing answers AlgContext ctx; ctx.Publish(kRawRequestIds, std::vector{1001}); - CompanyEntityOutputStruct out{}; - void* outputs[] = {&out}; + CompanyOperatorEntityOutput out{}; ExternalOutputBatchView view; - view.items = outputs; view.count = 1; - view.capacity = 1; + view.leased_slots["entity_out"] = {&out}; + view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + OutputPortBindings bindings( {{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}}); OutputEncodeOptions options; - options.converter_id = "translate.json.cabi.v1"; + options.converter_id = "translate.json.operator.v1"; + options.transport = "operator"; size_t written = 0; AdapterStatus status; @@ -861,7 +949,7 @@ TEST_F(AdapterContractSecurityTest, TEST_F(AdapterContractSecurityTest, TranslationSerializationFailurePriorityOverCapacity) { const auto* converter = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(converter, nullptr); // AlgContext with valid raw_req_ids, but answer has invalid UTF-8 byte @@ -871,14 +959,17 @@ TEST_F(AdapterContractSecurityTest, std::string invalid_utf8 = "prefix\xFF\xFFsuffix"; ctx.Publish(kLlmAnswers, TextBatch{{0, 0, invalid_utf8}}); + CompanyOperatorEntityOutput out{}; ExternalOutputBatchView view; - view.items = nullptr; - view.count = 0; - view.capacity = 0; + view.count = 1; + view.leased_slots["entity_out"] = {&out}; + view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + OutputPortBindings bindings( {{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}}); OutputEncodeOptions options; - options.converter_id = "translate.json.cabi.v1"; + options.converter_id = "translate.json.operator.v1"; + options.transport = "operator"; size_t written = 0; AdapterStatus status; @@ -893,23 +984,28 @@ TEST_F(AdapterContractSecurityTest, // RFC-0053 / RFC-0059: Translate null AlgContext diagnostics TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(out_conv, nullptr); // 1. Decode with null context: must return INVALID_INPUT (-3) with field // "context" - CompanyEntityInputStruct input{100, "{\"query\":\"test\"}"}; - const void* inputs[] = {&input}; + std::string query_json = "{\"query\":\"test\"}"; + CompanyString cs{static_cast(query_json.size()), + const_cast(query_json.data())}; + CompanyOperatorEntityInput input{100, &cs}; ExternalInputBatchView in_view; - in_view.items = inputs; in_view.count = 1; + in_view.leased_slots["entity_in"] = {&input}; + in_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; + InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions in_options; - in_options.converter_id = "translate.json.cabi.v1"; + in_options.converter_id = "translate.json.operator.v1"; + in_options.transport = "operator"; AdapterStatus unpack_status; int unpack_ret = in_conv->decode_fn(in_view, in_options, in_bindings, nullptr, @@ -917,7 +1013,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { EXPECT_EQ(unpack_ret, COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(unpack_status.Code(), COMPANY_ALG_ERR_INVALID_INPUT); EXPECT_EQ(unpack_status.FieldPath(), "context"); - EXPECT_EQ(unpack_status.AdapterName(), "translate.json.cabi.v1"); + EXPECT_EQ(unpack_status.AdapterName(), "translate.json.operator.v1"); EXPECT_EQ( in_conv->decode_fn(in_view, in_options, in_bindings, nullptr, nullptr), @@ -925,16 +1021,17 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { // 2. Encode with null context: must return BUFFER_TOO_SMALL (-4) with field // "context" - CompanyEntityOutputStruct output{}; - void* outputs[] = {&output}; + CompanyOperatorEntityOutput output{}; ExternalOutputBatchView out_view; - out_view.items = outputs; out_view.count = 1; - out_view.capacity = 1; + out_view.leased_slots["entity_out"] = {&output}; + out_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + OutputPortBindings out_bindings( {{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}}); OutputEncodeOptions out_options; - out_options.converter_id = "translate.json.cabi.v1"; + out_options.converter_id = "translate.json.operator.v1"; + out_options.transport = "operator"; size_t written = 0; AdapterStatus pack_status; @@ -943,7 +1040,7 @@ TEST_F(AdapterContractSecurityTest, TranslateNullContextDiagnostics) { EXPECT_EQ(pack_ret, COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(pack_status.Code(), COMPANY_ALG_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(pack_status.FieldPath(), "context"); - EXPECT_EQ(pack_status.AdapterName(), "translate.json.cabi.v1"); + EXPECT_EQ(pack_status.AdapterName(), "translate.json.operator.v1"); EXPECT_EQ(out_conv->encode_fn(nullptr, out_bindings, out_options, &out_view, &written, nullptr), diff --git a/tests/contract/abi/test_c11_abi_compliance.c b/tests/contract/abi/test_c11_abi_compliance.c deleted file mode 100644 index 5f08d316..00000000 --- a/tests/contract/abi/test_c11_abi_compliance.c +++ /dev/null @@ -1,168 +0,0 @@ -/** - * @file test_c11_abi_compliance.c - * @brief Pure C11 compilation and runtime ABI compliance test. - * - * This file is compiled with a pure C compiler (C11 standard) to guarantee - * that include/edgeflow/c_api.h exposes zero C++ symbols or STL - * dependencies. - */ - -#include -#include -#include - -// Platform mocks must be usable as C declarations before any SDK entrypoint. -#include "edgeflow/c_api.h" -#include "platform_mock/alg_types.h" -#include "platform_mock/error_codes.h" -#include "platform_mock/operator_data_types.h" - -#if COMPANY_ALG_ABI_VERSION_MAJOR != 6 -#error "Unexpected public C ABI major" -#endif -#include "edgeflow/log.h" -#include "edgeflow/operator/types.h" -#include "edgeflow/version.h" - -_Static_assert(sizeof(CompanyString) == sizeof(int32_t) + sizeof(char*) + - (sizeof(char*) == 8 ? 4 : 0), - "CompanyString memory layout check"); -_Static_assert(COMPANY_OPERATOR_MAX_RERANK_CANDIDATES == 8, - "COMPANY_OPERATOR_MAX_RERANK_CANDIDATES must be 8"); -_Static_assert(E_ALG_BASE_LOG_LEVEL_FATAL == 0, - "Fatal log level must remain 0"); -_Static_assert(E_ALG_BASE_LOG_LEVEL_WARNING == 2, - "Warning log level must remain 2"); -_Static_assert(E_ALG_BASE_LOG_LEVEL_VERBOSE == 5, - "Verbose log level must remain 5"); - -int main(void) { - if (strcmp(COMPANY_ALG_PRODUCT_VERSION, "10.0.0") != 0 || - strcmp(COMPANY_ALG_ABI_VERSION, "6.0.0") != 0) { - fprintf(stderr, "[C11 ABI Test] Generated version contract drifted\n"); - return 13; - } - if (AlgBase_getLogLevelByName("LLM_EDGEFLOW") != - E_ALG_BASE_LOG_LEVEL_WARNING) { - fprintf(stderr, "[C11 ABI Test] Public log default must be WARNING\n"); - return 11; - } - if (AlgBase_setLogLevelByName("LLM_EDGEFLOW", E_ALG_BASE_LOG_LEVEL_WARNING) != - 0 || - AlgBase_getLogLevelByName("LLM_EDGEFLOW") != - E_ALG_BASE_LOG_LEVEL_WARNING) { - fprintf(stderr, "[C11 ABI Test] Failed to configure public log API\n"); - return 12; - } - ALG_LOG_DEBUG("This C11 debug record is filtered\n"); - ALG_LOG_WARNING("[C11 ABI Test] Public log macro is operational\n"); - - printf("[C11 ABI Test] Testing pure C ABI lifecycle and safety...\n"); - - // 1. Test Alg_Init and Alg_DeInit - if (Alg_Init() != 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Init failed\n"); - return 1; - } - - // 2. Test Null Pointer Safety - if (Alg_Create(NULL, NULL) == 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Create should fail on null pointers\n"); - return 2; - } - - int num_out = 0; - if (Alg_Process(NULL, NULL, 0, NULL, &num_out) == 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Process should fail on null handle\n"); - return 3; - } - - if (Alg_Control(NULL, NULL) == 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Control should fail on null handle\n"); - return 4; - } - - if (Alg_Destroy(NULL) == 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Destroy should fail on null handle\n"); - return 5; - } - - // 3. Test handle creation and processing with keyword matching - CompanyAlgParamCreate param; - memset(¶m, 0, sizeof(param)); - - // Determine config path - const char* cfg_candidates[] = { - "configs/pipeline_keyword_match_cabi.json", - "../configs/pipeline_keyword_match_cabi.json", - "../../configs/pipeline_keyword_match_cabi.json"}; - const char* cfg_path = NULL; - for (int i = 0; i < 3; ++i) { - FILE* f = fopen(cfg_candidates[i], "r"); - if (f) { - fclose(f); - cfg_path = cfg_candidates[i]; - break; - } - } - - if (!cfg_path) { - fprintf(stderr, - "[C11 ABI Test] Could not find pipeline_keyword_match_cabi.json\n"); - return 6; - } - - param.config_file_path = cfg_path; - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = NULL; - int create_ret = Alg_Create(&handle, ¶m); - if (create_ret != 0 || !handle) { - fprintf(stderr, "[C11 ABI Test] Alg_Create failed with code: %d\n", - create_ret); - return 7; - } - - // 4. Test pure C batch processing - CompanyKeywordInputStruct req0 = {.request_id = 1001, - .sentence_text = "请联系VIP专员办理业务"}; - CompanyKeywordInputStruct req1 = {.request_id = 1002, - .sentence_text = "普通咨询业务"}; - const void* inputs[2] = {&req0, &req1}; - - CompanyKeywordOutputStruct out0; - CompanyKeywordOutputStruct out1; - memset(&out0, 0, sizeof(out0)); - memset(&out1, 0, sizeof(out1)); - void* outputs[2] = {&out0, &out1}; - - int num_outputs = 2; - int proc_ret = Alg_Process(handle, inputs, 2, outputs, &num_outputs); - if (proc_ret != 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Process failed with code: %d\n", - proc_ret); - Alg_Destroy(handle); - return 8; - } - - printf("[C11 ABI Test] Batch processed %d samples successfully.\n", - num_outputs); - printf("[C11 ABI Test] Sample 0: is_hit=%d, match_result=%s\n", out0.is_hit, - out0.match_result_json); - printf("[C11 ABI Test] Sample 1: is_hit=%d, match_result=%s\n", out1.is_hit, - out1.match_result_json); - - if (Alg_Destroy(handle) != 0) { - fprintf(stderr, "[C11 ABI Test] Alg_Destroy failed\n"); - return 9; - } - - if (Alg_DeInit() != 0) { - fprintf(stderr, "[C11 ABI Test] Alg_DeInit failed\n"); - return 10; - } - - printf("[C11 ABI Test] All pure C11 ABI tests passed successfully!\n"); - return 0; -} diff --git a/tests/contract/abi/test_c_abi_safety.cpp b/tests/contract/abi/test_c_abi_safety.cpp deleted file mode 100644 index 7a7fe2ca..00000000 --- a/tests/contract/abi/test_c_abi_safety.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "adapter/biz_blackboard_keys.h" -#include "adapter/io_binding_registry.h" -#include "adapter/io_converter_registry.h" -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" - -static std::string GetConfigPath(const std::string& rel_path) { - FILE* fp = fopen(rel_path.c_str(), "r"); - if (fp) { - fclose(fp); - return rel_path; - } - return "../" + rel_path; -} - -class CAbiSafetyTest : public ::testing::Test { - protected: - void SetUp() override { - llm_edgeflow::IoBindingRegistry::Instance().ResetConflictForTesting(); - llm_edgeflow::IoConverterRegistry::Instance().ResetConflictForTesting(); - Alg_Init(); - } - void TearDown() override { - Alg_DeInit(); - llm_edgeflow::IoBindingRegistry::Instance().ResetConflictForTesting(); - llm_edgeflow::IoConverterRegistry::Instance().ResetConflictForTesting(); - } -}; - -// 1. 测试空指针与异常安全防御机制 (noexcept barrier) -TEST_F(CAbiSafetyTest, NullPointerSafety) { - EXPECT_NE(Alg_Create(nullptr, nullptr), 0); - - CompanyAlgParamCreate param; - param.config_file_path = ""; - void* handle = nullptr; - EXPECT_NE(Alg_Create(&handle, ¶m), 0); - - std::vector inputs; - std::vector outputs; - EXPECT_NE(Alg_Process(nullptr, inputs, outputs), 0); - EXPECT_NE(Alg_Control(nullptr, nullptr), 0); - EXPECT_NE(Alg_Destroy(nullptr), 0); -} - -// 2. 测试句柄快速创建与销毁循环 (50轮生命周期与资源泄露检测) -TEST_F(CAbiSafetyTest, HandleLifecycleStressCycles50) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - for (int cycle = 0; cycle < 50; ++cycle) { - void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); - ASSERT_EQ(ret, 0) << "Failed to create handle at cycle " << cycle; - ASSERT_NE(handle, nullptr); - - ret = Alg_Destroy(handle); - EXPECT_EQ(ret, 0) << "Failed to destroy handle at cycle " << cycle; - } -} - -// 3. 测试通过 C ABI 接口全流程调用与动态控制规则生效 -TEST_F(CAbiSafetyTest, EndToEndDynamicControlAndVerification) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); - ASSERT_EQ(ret, 0); - ASSERT_NE(handle, nullptr); - - // 下发动态规则 - CompanyAlgParamControl ctrl; - ctrl.control_cmd = 1; - ctrl.json_param_str = "{\"categories\": {\"TEST_VIP\": [\"VIP\"]}}"; - ret = Alg_Control(handle, &ctrl); - EXPECT_EQ(ret, 0); - - // 执行推理 - CompanyKeywordInputStruct req0{101, "请联系VIP专员"}; - CompanyKeywordInputStruct req1{102, "普通闲聊文本"}; - std::vector inputs = {&req0, &req1}; - - CompanyKeywordOutputStruct out0; - CompanyKeywordOutputStruct out1; - std::vector outputs = {&out0, &out1}; - - ret = Alg_Process(handle, inputs, outputs); - EXPECT_EQ(ret, 0); - EXPECT_EQ(out0.is_hit, 1); - EXPECT_EQ(out1.is_hit, 0); - EXPECT_TRUE(std::string(out0.match_result_json).find("TEST_VIP") != - std::string::npos); - - ret = Alg_Destroy(handle); - EXPECT_EQ(ret, 0); -} - -// 4. 测试输出缓冲区容量不足与所需容量回填契约 (ACC-003) -TEST_F(CAbiSafetyTest, OutputCapacityInsufficientAndFeedbackContract) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); - - CompanyKeywordInputStruct req0{101, "请联系VIP专员"}; - CompanyKeywordInputStruct req1{102, "普通闲聊文本"}; - const void* inputs[2] = {&req0, &req1}; - - CompanyKeywordOutputStruct out0; - void* outputs[1] = {&out0}; - - // 1) 传入 outputs = nullptr 且 capacity = 0 (标准容量预查),必须返回 -4 - // 并回填所需容量为 2 - int num_outputs = 0; - int ret = Alg_Process(handle, inputs, 2, nullptr, &num_outputs); - EXPECT_EQ(ret, -4); - EXPECT_EQ(num_outputs, 2); - - // 2) 传入 outputs 有效但容量为 1 (小于需要的 2),应该返回 -4 并回填所需容量为 - // 2 - num_outputs = 1; - ret = Alg_Process(handle, inputs, 2, outputs, &num_outputs); - EXPECT_EQ(ret, -4); - EXPECT_EQ(num_outputs, 2); - - EXPECT_EQ(Alg_Destroy(handle), 0); -} - -// 5. 测试输入与输出空槽位确定性拦截 (ACC-003) -TEST_F(CAbiSafetyTest, NullSlotInBatchInputsOrOutputs) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); - - CompanyKeywordInputStruct req0{101, "请联系VIP专员"}; - const void* inputs_with_null[2] = {&req0, nullptr}; // 第二个槽位为空 - - CompanyKeywordOutputStruct out0, out1; - void* outputs[2] = {&out0, &out1}; - int num_outputs = 2; - - // 输入包含空指针 -> 必须确定性返回 -3 - int ret = Alg_Process(handle, inputs_with_null, 2, outputs, &num_outputs); - EXPECT_EQ(ret, -3); - - // 输出包含空指针 -> 必须确定性返回 -4 - const void* valid_inputs[2] = {&req0, &req0}; - void* outputs_with_null[2] = {&out0, nullptr}; - num_outputs = 2; - ret = Alg_Process(handle, valid_inputs, 2, outputs_with_null, &num_outputs); - EXPECT_EQ(ret, -4); - - EXPECT_EQ(Alg_Destroy(handle), 0); -} - -// 6. 测试 IoBinding 注册冲突防护与定义机器可读性 -TEST_F(CAbiSafetyTest, IoBindingRegistryConflictDetectionAndDescriptor) { - auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); - const auto* doc_binding = registry.FindBinding("doc_qa.cabi.v1"); - ASSERT_NE(doc_binding, nullptr); - - EXPECT_EQ(doc_binding->binding_id, "doc_qa.cabi.v1"); - EXPECT_EQ(doc_binding->biz_name, "smart_doc_qa_v1"); - EXPECT_EQ(doc_binding->transport, "cabi"); - EXPECT_GT(doc_binding->max_batch_size, 0); - - // 测试重复 binding 注册拦截 - bool reg_dup_ret = registry.RegisterBinding(*doc_binding); - EXPECT_FALSE(reg_dup_ret) << "Duplicate binding_id registration must fail"; - registry.ResetConflictForTesting(); -} - -// 7. 测试 RuntimeOptions 与设备参数贯通 (ACC-004) -TEST_F(CAbiSafetyTest, RuntimeOptionsAndDevicePropagation) { - std::string cfg = - GetConfigPath("demo/fixtures/mock/pipeline_doc_qa_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; // 显式指定设备 0 - - void* handle0 = nullptr; - ASSERT_EQ(Alg_Create(&handle0, ¶m), 0); - EXPECT_EQ(Alg_Destroy(handle0), 0); - - param.device_id = 1; // 显式指定设备 1 - void* handle1 = nullptr; - ASSERT_EQ(Alg_Create(&handle1, ¶m), 0); - EXPECT_EQ(Alg_Destroy(handle1), 0); -} - -// 8. 测试配置中未知/缺失 binding 在 Alg_Create 前置拦截 -TEST_F(CAbiSafetyTest, UnknownAndUnregisteredBindingRejectionInCreate) { - // 1) 传入不存在的接入配置 - CompanyAlgParamCreate param; - param.config_file_path = "non_existent_cabi_config.json"; - param.model_root_dir = "./models"; - param.device_id = 0; - void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); - - // 2) 传入缺失 io_binding 的配置 - std::string bad_cfg = "./results/test_missing_binding.json"; - std::filesystem::create_directories("./results"); - { - std::ofstream ofs(bad_cfg); - ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json"}})"; - } - param.config_file_path = bad_cfg.c_str(); - ret = Alg_Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); - - // 3) 传入未知 io_binding - { - std::ofstream ofs(bad_cfg); - ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json", "io_binding": "unknown.binding.v999"}})"; - } - ret = Alg_Create(&handle, ¶m); - EXPECT_NE(ret, 0); - EXPECT_EQ(handle, nullptr); - std::filesystem::remove(bad_cfg); -} - -// 9. 测试 Registry 冲突 fail-closed 导致 Alg_Init 失败 -TEST_F(CAbiSafetyTest, FailClosedRegistryConflictAndInitFailure) { - auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); - registry.ResetConflictForTesting(); - - // 初始干净状态 Alg_Init 成功 - EXPECT_EQ(Alg_Init(), 0); - - // 注册冲突(重复注册 DocQA 绑定) - const auto* binding = registry.FindBinding("doc_qa.cabi.v1"); - ASSERT_NE(binding, nullptr); - bool reg_ret = registry.RegisterBinding(*binding); - EXPECT_FALSE(reg_ret); - EXPECT_TRUE(registry.HasConflict()); - - // 注册冲突发生后,Alg_Init 必须 fail-closed 返回 -6 - EXPECT_EQ(Alg_Init(), -6); - - // 测试结束后清理恢复干净状态 - registry.ResetConflictForTesting(); - EXPECT_FALSE(registry.HasConflict()); - EXPECT_EQ(Alg_Init(), 0); -} - -// 10. 测试有效批次上限契约强制执行 -TEST_F(CAbiSafetyTest, AdapterDescriptorMaxBatchSizeEnforcement) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); - - // 构造 65 条输入数据 (超过 max_batch_size = 64 上限) - std::vector reqs(65, {1, "测试输入"}); - std::vector inputs(65); - for (int i = 0; i < 65; ++i) inputs[i] = &reqs[i]; - - std::vector outs(65); - std::vector outputs(65); - for (int i = 0; i < 65; ++i) outputs[i] = &outs[i]; - int num_outputs = 65; - - // 超过 max_batch_size -> 必须被 ValidateBatchPreFlight 前置拦截返回 -3 - int ret = - Alg_Process(handle, inputs.data(), 65, outputs.data(), &num_outputs); - EXPECT_EQ(ret, -3); - - EXPECT_EQ(Alg_Destroy(handle), 0); -} - -// 11. 同一 handle 的并发 Process 由接入适配层串行化,停流 join 后才允许 Destroy -TEST_F(CAbiSafetyTest, SameHandleConcurrentProcessAndQuiescedDestroy) { - std::string cfg = GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg.c_str(); - param.model_root_dir = "./models"; - param.device_id = 0; - - void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); - ASSERT_NE(handle, nullptr); - - constexpr int kThreadCount = 8; - constexpr int kCallsPerThread = 40; - std::atomic start{false}; - std::atomic failures{0}; - std::vector workers; - workers.reserve(kThreadCount); - - for (int thread_index = 0; thread_index < kThreadCount; ++thread_index) { - workers.emplace_back([&, thread_index]() { - while (!start.load(std::memory_order_acquire)) { - std::this_thread::yield(); - } - for (int call_index = 0; call_index < kCallsPerThread; ++call_index) { - const uint64_t request_id = - static_cast(thread_index * kCallsPerThread + call_index); - CompanyKeywordInputStruct input{request_id, "same handle request"}; - CompanyKeywordOutputStruct output{}; - const void* inputs[1] = {&input}; - void* outputs[1] = {&output}; - int num_outputs = 1; - const int ret = Alg_Process(handle, inputs, 1, outputs, &num_outputs); - if (ret != COMPANY_ALG_SUCCESS || num_outputs != 1 || - output.request_id != request_id) { - failures.fetch_add(1, std::memory_order_relaxed); - } - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& worker : workers) { - worker.join(); - } - - EXPECT_EQ(failures.load(), 0); - EXPECT_EQ(Alg_Destroy(handle), 0); -} - -// 12. RFC-0053: Entity 失败样本在结构化校验失败时,先写 request_id,但 status -// 与 entities_json 保留原调用者哨兵值 -TEST_F(CAbiSafetyTest, EntityFailureSampleSentinelValues) { - const auto* out_conv = - llm_edgeflow::IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.cabi.v1"); - ASSERT_NE(out_conv, nullptr); - - llm_edgeflow::AlgContext ctx; - ctx.Publish(llm_edgeflow::kRawRequestIds, std::vector{1001, 2002}); - - llm_edgeflow::StructuredDocumentBatch entities; - entities.emplace_back( - 0, 0, - llm_edgeflow::JsonDocumentItem("[\"valid_entity\"]", true, - llm_edgeflow::JsonParseStatus::kOk)); - entities.emplace_back( - 1, 0, - llm_edgeflow::JsonDocumentItem("invalid", false, - llm_edgeflow::JsonParseStatus::kFailed)); - ctx.Publish(llm_edgeflow::kExtractedEntities, std::move(entities)); - - CompanyEntityOutputStruct out0{}, out1{}; - out1.request_id = 99999; - out1.status_code = -777; - std::strcpy(out1.entities_json, "SENTINEL_PAYLOAD"); - - void* outputs[2] = {&out0, &out1}; - llm_edgeflow::ExternalOutputBatchView out_view; - out_view.items = outputs; - out_view.count = 2; - out_view.capacity = 2; - out_view.type_id = out_conv->external_type; - - llm_edgeflow::OutputEncodeOptions options; - options.binding_id = "entity_extract.cabi.v1"; - options.converter_id = out_conv->converter_id; - options.transport = "cabi"; - - llm_edgeflow::OutputPortBindings bindings( - {{"raw_request_ids", "raw_request_ids"}, - {"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); - - EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); - EXPECT_EQ(out0.request_id, 1001u); - EXPECT_EQ(out0.status_code, 0); - EXPECT_STREQ(out0.entities_json, "[\"valid_entity\"]"); - - // Sample 1: request_id was written, but status_code and entities_json - // retained sentinels - EXPECT_EQ(out1.request_id, 2002u); - EXPECT_EQ(out1.status_code, -777); - EXPECT_STREQ(out1.entities_json, "SENTINEL_PAYLOAD"); -} diff --git a/tests/contract/abi/test_cpp_operator_sdk.cpp b/tests/contract/abi/test_cpp_operator_sdk.cpp new file mode 100644 index 00000000..21f770a5 --- /dev/null +++ b/tests/contract/abi/test_cpp_operator_sdk.cpp @@ -0,0 +1,218 @@ +/** + * @file test_cpp_operator_sdk.cpp + * @brief Public C++ Operator SDK consumer test (RFC-0060). + * + * This file verifies that an external C++ consumer can link against + * llm_edgeflow::sdk using solely the public SDK headers, without any + * internal headers, runtime objects, or test mocks. + */ + +#include +#include +#include +#include +#include +#include + +#include "edgeflow/export.h" +#include "edgeflow/log.h" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" +#include "edgeflow/version.h" +#include "platform_mock/error_codes.h" +#include "platform_mock/operator_data_types.h" +#include "platform_mock/operator_types.h" + +// Public layout and contract assertions +static_assert(sizeof(CompanyString) == sizeof(int32_t) + sizeof(char*) + + (sizeof(char*) == 8 ? 4 : 0), + "CompanyString memory layout check"); +static_assert(COMPANY_OPERATOR_MAX_RERANK_CANDIDATES == 8, + "COMPANY_OPERATOR_MAX_RERANK_CANDIDATES must be 8"); +static_assert(E_ALG_BASE_LOG_LEVEL_FATAL == 0, "Fatal log level must remain 0"); +static_assert(E_ALG_BASE_LOG_LEVEL_WARNING == 2, + "Warning log level must remain 2"); +static_assert(E_ALG_BASE_LOG_LEVEL_VERBOSE == 5, + "Verbose log level must remain 5"); + +int main() { + // 1. Version contract check + if (std::strcmp(COMPANY_ALG_PRODUCT_VERSION, "11.0.0") != 0 || + std::strcmp(COMPANY_ALG_ABI_VERSION, "7.0.0") != 0 || + COMPANY_ALG_ABI_VERSION_MAJOR != 7) { + std::fprintf(stderr, + "[SDK Consumer Test] Generated version contract drifted\n"); + return 1; + } + + // 2. Logging API check + if (AlgBase_getLogLevelByName("LLM_EDGEFLOW") != + E_ALG_BASE_LOG_LEVEL_WARNING) { + std::fprintf(stderr, + "[SDK Consumer Test] Public log default must be WARNING\n"); + return 2; + } + if (AlgBase_setLogLevelByName("LLM_EDGEFLOW", E_ALG_BASE_LOG_LEVEL_WARNING) != + 0 || + AlgBase_getLogLevelByName("LLM_EDGEFLOW") != + E_ALG_BASE_LOG_LEVEL_WARNING) { + std::fprintf(stderr, + "[SDK Consumer Test] Failed to configure public log API\n"); + return 3; + } + ALG_LOG_DEBUG("This debug record is filtered\n"); + ALG_LOG_WARNING("[SDK Consumer Test] Public log macro is operational\n"); + + // 3. Operator table inspection + auto op = llm_edgeflow::operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + if (!op.Init || !op.Create || !op.Process || !op.Control || !op.Destroy || + !op.Deinit) { + std::fprintf( + stderr, + "[SDK Consumer Test] Operator function table has null entries\n"); + return 4; + } + + // 4. Init + if (op.Init() != 0) { + std::fprintf(stderr, "[SDK Consumer Test] op.Init failed\n"); + return 5; + } + + // Locate configs directory + const char* root_candidates[] = {".", "..", "../.."}; + const char* config_rel = "configs/pipeline_keyword_match_rules.conf"; + std::string root_dir; + for (const char* r : root_candidates) { + std::string test_p = std::string(r) + "/" + config_rel; + FILE* fp = std::fopen(test_p.c_str(), "r"); + if (fp) { + std::fclose(fp); + root_dir = r; + break; + } + } + if (root_dir.empty()) { + std::fprintf(stderr, "[SDK Consumer Test] Could not find %s\n", config_rel); + return 6; + } + + // 5. ValidateOperatorConfigBinding + char err_buf[512] = {0}; + int val_ret = llm_edgeflow::operator_api::ValidateOperatorConfigBinding( + root_dir.c_str(), config_rel, "keyword_match.operator.v1", err_buf, + sizeof(err_buf)); + if (val_ret != 0) { + std::fprintf( + stderr, + "[SDK Consumer Test] ValidateOperatorConfigBinding failed: %s\n", + err_buf); + return 7; + } + + // 6. Create + llm_edgeflow::operator_api::CreateParam create_param{}; + create_param.model_path = root_dir.c_str(); + create_param.cfg_file_name = config_rel; + create_param.device_id = 0; + create_param.compute_platform = + llm_edgeflow::operator_api::ComputePlatform::kCpu; + create_param.max_frame_depth = 25; + + void* handle = nullptr; + if (op.Create(&handle, &create_param) != 0 || !handle) { + std::fprintf(stderr, "[SDK Consumer Test] op.Create failed: %s\n", + llm_edgeflow::operator_api::GetOperatorLastError()); + return 8; + } + + // 7. Process + std::string text1 = "客户要求加急处理VIP订单"; + CompanyString cs1{static_cast(text1.size()), + const_cast(text1.data())}; + CompanyOperatorKeywordInput in_req1{}; + in_req1.request_id = 1001; + in_req1.sentence_text = &cs1; + + llm_edgeflow::operator_api::NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + llm_edgeflow::operator_api::MakeBorrowedOperatorInput(&in_req1); + + llm_edgeflow::operator_api::NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; + + if (op.Process(handle, inputs, outputs) != 0) { + std::fprintf(stderr, "[SDK Consumer Test] op.Process failed: %s\n", + llm_edgeflow::operator_api::GetOperatorLastError()); + return 9; + } + + auto out_sp = outputs[0]["client_channel.keyword_out"]; + if (!out_sp) { + std::fprintf( + stderr, + "[SDK Consumer Test] Output slot client_channel.keyword_out is null\n"); + return 10; + } + auto* out_dto = static_cast(out_sp.get()); + if (out_dto->request_id != 1001 || out_dto->status_code != 0) { + std::fprintf( + stderr, + "[SDK Consumer Test] Unexpected output values: req_id=%lu status=%d\n", + static_cast(out_dto->request_id), out_dto->status_code); + return 11; + } + + // 8. Control: update rule categories + llm_edgeflow::operator_api::ControlUpdateRulesParam rules_param{ + "{\"categories\":{\"URGENT\":[\"加急\"]}}"}; + if (op.Control(handle, + llm_edgeflow::operator_api::ControlCommand::kUpdateRules, + &rules_param) != 0) { + std::fprintf(stderr, "[SDK Consumer Test] op.Control failed: %s\n", + llm_edgeflow::operator_api::GetOperatorLastError()); + return 12; + } + + // 9. Process again and verify rule change took effect + outputs[0]["client_channel.keyword_out"] = nullptr; + if (op.Process(handle, inputs, outputs) != 0) { + std::fprintf(stderr, "[SDK Consumer Test] Second op.Process failed: %s\n", + llm_edgeflow::operator_api::GetOperatorLastError()); + return 13; + } + out_sp = outputs[0]["client_channel.keyword_out"]; + out_dto = static_cast(out_sp.get()); + if (out_dto->is_hit != 1) { + std::fprintf( + stderr, "[SDK Consumer Test] Expected keyword hit after rule update\n"); + return 14; + } + + // 10. Copy fields and release all leases before destroy + std::string match_copy; + if (out_dto->match_result_json && out_dto->match_result_json->data) { + match_copy.assign(out_dto->match_result_json->data, + out_dto->match_result_json->length); + } + out_sp.reset(); + outputs.clear(); + inputs.clear(); + + // 11. Destroy handle + if (op.Destroy(handle) != 0) { + std::fprintf(stderr, "[SDK Consumer Test] op.Destroy failed: %s\n", + llm_edgeflow::operator_api::GetOperatorLastError()); + return 15; + } + + // 12. Deinit + if (op.Deinit() != 0) { + std::fprintf(stderr, "[SDK Consumer Test] op.Deinit failed\n"); + return 16; + } + + std::printf( + "[SDK Consumer Test] All C++ Operator SDK consumer tests passed.\n"); + return 0; +} diff --git a/tests/contract/abi/test_operator_safety.cpp b/tests/contract/abi/test_operator_safety.cpp new file mode 100644 index 00000000..084ef51f --- /dev/null +++ b/tests/contract/abi/test_operator_safety.cpp @@ -0,0 +1,496 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adapter/biz_blackboard_keys.h" +#include "adapter/biz_results.h" +#include "adapter/io_binding_registry.h" +#include "adapter/io_converter_registry.h" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" +#include "platform_mock/error_codes.h" + +using namespace llm_edgeflow::operator_api; + +class OperatorSafetyTest : public ::testing::Test { + protected: + void SetUp() override { + llm_edgeflow::IoBindingRegistry::Instance().ResetConflictForTesting(); + llm_edgeflow::IoConverterRegistry::Instance().ResetConflictForTesting(); + Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + llm_edgeflow::IoBindingRegistry::Instance().ResetConflictForTesting(); + llm_edgeflow::IoConverterRegistry::Instance().ResetConflictForTesting(); + } +}; + +// 1. 测试空指针与异常安全防御机制 (noexcept barrier) +TEST_F(OperatorSafetyTest, NullPointerSafety) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + EXPECT_NE(op.Create(nullptr, nullptr), 0); + + CreateParam param{}; + param.cfg_file_name = ""; + void* handle = nullptr; + EXPECT_NE(op.Create(&handle, ¶m), 0); + + NamedIoBatch inputs; + NamedIoBatch outputs; + EXPECT_NE(op.Process(nullptr, inputs, outputs), 0); + EXPECT_NE(op.Control(nullptr, ControlCommand::kUpdateRules, nullptr), 0); + EXPECT_NE(op.Destroy(nullptr), 0); +} + +// 2. 测试句柄快速创建与销毁循环 (50轮生命周期与资源泄露检测) +TEST_F(OperatorSafetyTest, HandleLifecycleStressCycles50) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + for (int cycle = 0; cycle < 50; ++cycle) { + void* handle = nullptr; + int ret = op.Create(&handle, ¶m); + ASSERT_EQ(ret, 0) << "Failed to create handle at cycle " << cycle; + ASSERT_NE(handle, nullptr); + + ret = op.Destroy(handle); + EXPECT_EQ(ret, 0) << "Failed to destroy handle at cycle " << cycle; + } +} + +// 3. 测试通过 Operator 接口全流程调用与动态控制规则生效 +TEST_F(OperatorSafetyTest, EndToEndDynamicControlAndVerification) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + void* handle = nullptr; + int ret = op.Create(&handle, ¶m); + ASSERT_EQ(ret, 0); + ASSERT_NE(handle, nullptr); + + // 下发动态规则 + ControlUpdateRulesParam ctrl{"{\"categories\": {\"TEST_VIP\": [\"VIP\"]}}"}; + ret = op.Control(handle, ControlCommand::kUpdateRules, &ctrl); + EXPECT_EQ(ret, 0); + + // 执行推理 + std::string s0 = "请联系VIP专员"; + std::string s1 = "普通闲聊文本"; + CompanyString cs0{static_cast(s0.size()), + const_cast(s0.data())}; + CompanyString cs1{static_cast(s1.size()), + const_cast(s1.data())}; + + CompanyOperatorKeywordInput req0{101, &cs0}; + CompanyOperatorKeywordInput req1{102, &cs1}; + + NamedIoBatch inputs(2); + inputs[0]["client_channel.keyword_in"] = MakeBorrowedOperatorInput(&req0); + inputs[1]["client_channel.keyword_in"] = MakeBorrowedOperatorInput(&req1); + + NamedIoBatch outputs(2); + outputs[0]["client_channel.keyword_out"] = nullptr; + outputs[1]["client_channel.keyword_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); + EXPECT_EQ(ret, 0); + + auto out0_sp = outputs[0]["client_channel.keyword_out"]; + auto out1_sp = outputs[1]["client_channel.keyword_out"]; + ASSERT_NE(out0_sp, nullptr); + ASSERT_NE(out1_sp, nullptr); + + auto* out0 = static_cast(out0_sp.get()); + auto* out1 = static_cast(out1_sp.get()); + + EXPECT_EQ(out0->is_hit, 1); + EXPECT_EQ(out1->is_hit, 0); + ASSERT_NE(out0->match_result_json, nullptr); + EXPECT_TRUE(std::string(out0->match_result_json->data, + out0->match_result_json->length) + .find("TEST_VIP") != std::string::npos); + + out0_sp.reset(); + out1_sp.reset(); + outputs.clear(); + ret = op.Destroy(handle); + EXPECT_EQ(ret, 0); +} + +// 4. 测试输出批次数量不匹配拦截契约 +TEST_F(OperatorSafetyTest, OutputBatchSizeMismatchProtection) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + void* handle = nullptr; + ASSERT_EQ(op.Create(&handle, ¶m), 0); + + std::string s0 = "请联系VIP专员"; + std::string s1 = "普通闲聊文本"; + CompanyString cs0{static_cast(s0.size()), + const_cast(s0.data())}; + CompanyString cs1{static_cast(s1.size()), + const_cast(s1.data())}; + + CompanyOperatorKeywordInput req0{101, &cs0}; + CompanyOperatorKeywordInput req1{102, &cs1}; + + NamedIoBatch inputs(2); + inputs[0]["client_channel.keyword_in"] = MakeBorrowedOperatorInput(&req0); + inputs[1]["client_channel.keyword_in"] = MakeBorrowedOperatorInput(&req1); + + // 1) outputs 为空,批大小不匹配,必须返回错误 + NamedIoBatch empty_outputs; + int ret = op.Process(handle, inputs, empty_outputs); + EXPECT_NE(ret, 0); + + // 2) outputs 大小为 1 (小于需要的 2),必须返回错误 + NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; + ret = op.Process(handle, inputs, outputs); + EXPECT_NE(ret, 0); + + EXPECT_EQ(op.Destroy(handle), 0); +} + +// 5. 测试输入包含缺失槽位确定性拦截 +TEST_F(OperatorSafetyTest, NullOrMissingSlotInBatchInputs) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + void* handle = nullptr; + ASSERT_EQ(op.Create(&handle, ¶m), 0); + + std::string s0 = "请联系VIP专员"; + CompanyString cs0{static_cast(s0.size()), + const_cast(s0.data())}; + CompanyOperatorKeywordInput req0{101, &cs0}; + + NamedIoBatch inputs_with_missing_slot(2); + inputs_with_missing_slot[0]["client_channel.keyword_in"] = + MakeBorrowedOperatorInput(&req0); + // 第二个样本缺少必需槽位 client_channel.keyword_in + + NamedIoBatch outputs(2); + outputs[0]["client_channel.keyword_out"] = nullptr; + outputs[1]["client_channel.keyword_out"] = nullptr; + + int ret = op.Process(handle, inputs_with_missing_slot, outputs); + EXPECT_NE(ret, 0); + + EXPECT_EQ(op.Destroy(handle), 0); +} + +// 6. 测试 IoBinding 注册冲突防护与定义机器可读性 +TEST_F(OperatorSafetyTest, IoBindingRegistryConflictDetectionAndDescriptor) { + auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); + const auto* binding = registry.FindBinding("keyword_match.operator.v1"); + ASSERT_NE(binding, nullptr); + + EXPECT_EQ(binding->binding_id, "keyword_match.operator.v1"); + EXPECT_EQ(binding->biz_name, "keyword_match_v1"); + EXPECT_EQ(binding->transport, "operator"); + EXPECT_GT(binding->max_batch_size, 0); + + // 测试重复 binding 注册拦截 + bool reg_dup_ret = registry.RegisterBinding(*binding); + EXPECT_FALSE(reg_dup_ret) << "Duplicate binding_id registration must fail"; + registry.ResetConflictForTesting(); + + // 测试非 operator transport 注册被拒绝 + llm_edgeflow::IoBindingDefinition bad_binding = *binding; + bad_binding.binding_id = "test.cabi.forbidden"; + bad_binding.transport = "cabi"; + EXPECT_FALSE(registry.RegisterBinding(bad_binding)); + EXPECT_TRUE(registry.HasConflict()); + registry.ResetConflictForTesting(); +} + +// 7. 测试 RuntimeOptions 与设备参数贯通 +TEST_F(OperatorSafetyTest, RuntimeOptionsAndDevicePropagation) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.compute_platform = ComputePlatform::kCpu; + param.device_id = 0; // 显式指定设备 0 + + void* handle0 = nullptr; + ASSERT_EQ(op.Create(&handle0, ¶m), 0); + EXPECT_EQ(op.Destroy(handle0), 0); + + param.device_id = 1; // 显式指定设备 1 + void* handle1 = nullptr; + ASSERT_EQ(op.Create(&handle1, ¶m), 0); + EXPECT_EQ(op.Destroy(handle1), 0); +} + +// 8. 测试配置中未知/缺失 binding 在 Create 前置拦截 +TEST_F(OperatorSafetyTest, UnknownAndUnregisteredBindingRejectionInCreate) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + + // 1) 传入不存在的接入配置 + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "non_existent_config.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + void* handle = nullptr; + int ret = op.Create(&handle, ¶m); + EXPECT_NE(ret, 0); + EXPECT_EQ(handle, nullptr); + + // 2) 传入缺失 io_binding 的配置 + std::string bad_cfg = "./results/test_missing_binding.conf"; + std::filesystem::create_directories("./results"); + { + std::ofstream ofs(bad_cfg); + ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json"}})"; + } + param.cfg_file_name = bad_cfg.c_str(); + ret = op.Create(&handle, ¶m); + EXPECT_NE(ret, 0); + EXPECT_EQ(handle, nullptr); + + // 3) 传入未知 io_binding + { + std::ofstream ofs(bad_cfg); + ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json", "io_binding": "unknown.binding.v999"}})"; + } + ret = op.Create(&handle, ¶m); + EXPECT_NE(ret, 0); + EXPECT_EQ(handle, nullptr); + + // 4) 传入旧 cabi 绑定 + { + std::ofstream ofs(bad_cfg); + ofs << R"({"schema_version": 1, "data": {"pipe_path": "pipeline_keyword_match_rules.json", "io_binding": "keyword_match.cabi.v1"}})"; + } + ret = op.Create(&handle, ¶m); + EXPECT_NE(ret, 0); + EXPECT_EQ(handle, nullptr); + + std::filesystem::remove(bad_cfg); +} + +// 9. 测试 Registry 冲突 fail-closed 导致 Init 失败 +TEST_F(OperatorSafetyTest, FailClosedRegistryConflictAndInitFailure) { + auto& registry = llm_edgeflow::IoBindingRegistry::Instance(); + registry.ResetConflictForTesting(); + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + + // 初始干净状态 Init 成功 + EXPECT_EQ(op.Init(), 0); + + // 注册冲突(重复注册 binding) + const auto* binding = registry.FindBinding("keyword_match.operator.v1"); + ASSERT_NE(binding, nullptr); + bool reg_ret = registry.RegisterBinding(*binding); + EXPECT_FALSE(reg_ret); + EXPECT_TRUE(registry.HasConflict()); + + // 注册冲突发生后,Init 必须 fail-closed 返回 -6 + EXPECT_EQ(op.Init(), -6); + + // 测试结束后清理恢复干净状态 + registry.ResetConflictForTesting(); + EXPECT_FALSE(registry.HasConflict()); + EXPECT_EQ(op.Init(), 0); +} + +// 10. 测试有效批次上限契约强制执行 +TEST_F(OperatorSafetyTest, AdapterDescriptorMaxBatchSizeEnforcement) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + void* handle = nullptr; + ASSERT_EQ(op.Create(&handle, ¶m), 0); + + // 构造 65 条输入数据 (超过 max_batch_size = 64 上限) + std::string s = "测试输入"; + CompanyString cs{static_cast(s.size()), const_cast(s.data())}; + std::vector reqs(65); + NamedIoBatch inputs(65); + NamedIoBatch outputs(65); + for (int i = 0; i < 65; ++i) { + reqs[i].request_id = i + 1; + reqs[i].sentence_text = &cs; + inputs[i]["client_channel.keyword_in"] = + MakeBorrowedOperatorInput(&reqs[i]); + outputs[i]["client_channel.keyword_out"] = nullptr; + } + + // 超过 max_batch_size -> 必须被前置拦截返回错误 + int ret = op.Process(handle, inputs, outputs); + EXPECT_NE(ret, 0); + + EXPECT_EQ(op.Destroy(handle), 0); +} + +// 11. 同一 handle 的并发 Process 由接入适配层串行化,停流 join 后才允许 Destroy +TEST_F(OperatorSafetyTest, SameHandleConcurrentProcessAndQuiescedDestroy) { + auto op = Get_LLM_EDGEFLOW_OperatorTable(); + CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; + param.device_id = 0; + param.compute_platform = ComputePlatform::kCpu; + param.max_frame_depth = 25; + + void* handle = nullptr; + ASSERT_EQ(op.Create(&handle, ¶m), 0); + ASSERT_NE(handle, nullptr); + + constexpr int kThreadCount = 8; + constexpr int kCallsPerThread = 40; + std::atomic start{false}; + std::atomic failures{0}; + std::vector workers; + workers.reserve(kThreadCount); + + for (int thread_index = 0; thread_index < kThreadCount; ++thread_index) { + workers.emplace_back([&, thread_index]() { + while (!start.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + for (int call_index = 0; call_index < kCallsPerThread; ++call_index) { + const uint64_t request_id = + static_cast(thread_index * kCallsPerThread + call_index); + std::string s = "same handle request"; + CompanyString cs{static_cast(s.size()), + const_cast(s.data())}; + CompanyOperatorKeywordInput input{request_id, &cs}; + + NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + MakeBorrowedOperatorInput(&input); + NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; + + const int ret = op.Process(handle, inputs, outputs); + if (ret != 0) { + failures.fetch_add(1, std::memory_order_relaxed); + continue; + } + auto out_sp = outputs[0]["client_channel.keyword_out"]; + if (!out_sp) { + failures.fetch_add(1, std::memory_order_relaxed); + continue; + } + auto* out_dto = + static_cast(out_sp.get()); + if (out_dto->request_id != request_id) { + failures.fetch_add(1, std::memory_order_relaxed); + } + outputs.clear(); + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& worker : workers) { + worker.join(); + } + + EXPECT_EQ(failures.load(), 0); + EXPECT_EQ(op.Destroy(handle), 0); +} + +// 12. RFC-0053: Entity 失败样本在结构化校验失败时,先写 request_id,但 status +// 与 entities_json 保留原调用者哨兵值 +TEST_F(OperatorSafetyTest, EntityFailureSampleSentinelValues) { + const auto* out_conv = + llm_edgeflow::IoConverterRegistry::Instance().FindOutputConverter( + "document.structured.operator.v1"); + ASSERT_NE(out_conv, nullptr); + + llm_edgeflow::AlgContext ctx; + ctx.Publish(llm_edgeflow::kRawRequestIds, std::vector{1001, 2002}); + + llm_edgeflow::StructuredDocumentBatch entities; + entities.emplace_back( + 0, 0, + llm_edgeflow::JsonDocumentItem("[\"valid_entity\"]", true, + llm_edgeflow::JsonParseStatus::kOk)); + entities.emplace_back( + 1, 0, + llm_edgeflow::JsonDocumentItem("invalid", false, + llm_edgeflow::JsonParseStatus::kFailed)); + ctx.Publish(llm_edgeflow::kExtractedEntities, std::move(entities)); + + char buf0[512] = {0}; + char buf1[512] = {0}; + std::strcpy(buf1, "SENTINEL_PAYLOAD"); + CompanyString cs0{0, buf0}; + CompanyString cs1{static_cast(std::strlen("SENTINEL_PAYLOAD")), + buf1}; + + CompanyOperatorEntityOutput out0{}, out1{}; + out0.entities_json = &cs0; + out1.entities_json = &cs1; + out1.request_id = 99999; + out1.status_code = -777; + + llm_edgeflow::ExternalOutputBatchView out_view; + out_view.count = 2; + out_view.leased_slots["entity_out"] = {&out0, &out1}; + out_view.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; + out_view.slot_capacities["entity_out"]["entities_json"] = 511; + + llm_edgeflow::OutputEncodeOptions options; + options.binding_id = "entity_extract.operator.v1"; + options.converter_id = out_conv->converter_id; + options.transport = "operator"; + + llm_edgeflow::OutputPortBindings bindings( + {{"raw_request_ids", "raw_request_ids"}, + {"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); + + EXPECT_EQ(ret, COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ(out0.request_id, 1001u); + EXPECT_EQ(out0.status_code, 0); + EXPECT_STREQ(out0.entities_json->data, "[\"valid_entity\"]"); + + // Sample 1: request_id was written, but status_code and entities_json + // retained sentinels + EXPECT_EQ(out1.request_id, 2002u); + EXPECT_EQ(out1.status_code, -777); + EXPECT_STREQ(out1.entities_json->data, "SENTINEL_PAYLOAD"); +} diff --git a/tests/contract/architecture/test_layer_header_views.cmake b/tests/contract/architecture/test_layer_header_views.cmake index 90d55791..25f55521 100644 --- a/tests/contract/architecture/test_layer_header_views.cmake +++ b/tests/contract/architecture/test_layer_header_views.cmake @@ -48,8 +48,8 @@ check_header(integration adapter/adapter_batch.h TRUE) check_header(integration adapter/text_carrier.h TRUE) check_header(capability_nodes core/node_definition.h TRUE) check_header(capability_nodes core/port_definition.h TRUE) -check_header(public edgeflow/c_api.h TRUE) -check_header(public edgeflow/c_api.hpp TRUE) +check_header(public edgeflow/c_api.h FALSE) +check_header(public edgeflow/c_api.hpp FALSE) check_header(public edgeflow/operator/interface.h TRUE) check_header(public edgeflow/operator/types.h TRUE) check_header(public company_alg_interface.h FALSE) @@ -82,7 +82,9 @@ check_header(extension engine/model_interface.h TRUE) check_header(extension adapter/shared_algorithm_runtime.h FALSE) check_header(extension adapter/operator/operator_output_pool.h FALSE) -foreach(header alg_types.h error_codes.h operator_data_types.h operator_types.h) +check_header(public platform_mock/alg_types.h FALSE) +check_header(integration platform_mock/alg_types.h FALSE) +foreach(header error_codes.h operator_data_types.h operator_types.h) check_header(public platform_mock/${header} TRUE) check_header(integration platform_mock/${header} TRUE) foreach(layer model_execution capability_nodes orchestration) diff --git a/tests/e2e/real_models/test_real_models_e2e.cpp b/tests/e2e/real_models/test_real_models_e2e.cpp index 9f105492..05670ec1 100644 --- a/tests/e2e/real_models/test_real_models_e2e.cpp +++ b/tests/e2e/real_models/test_real_models_e2e.cpp @@ -4,13 +4,15 @@ #include #include #include +#include #include #include -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" #include "engine/model_interface.h" #include "engine/model_runtime_factory.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { @@ -108,8 +110,8 @@ TEST_F(RealModelE2ETest, RealQwenBatchExecutionWithPadding) { } } -// 3. 真实模型接入 C ABI 全链路端到端验证 -TEST_F(RealModelE2ETest, RealModelCAbiEndToEnd) { +// 3. 真实模型接入 Operator 全链路端到端验证 +TEST_F(RealModelE2ETest, RealModelOperatorEndToEnd) { std::vector sentences = { "李雷在微软北京研发中心负责AI大模型芯片开发。"}; std::ifstream corpus(project_root_ / "data/corpus_entity_extract.txt"); @@ -121,43 +123,54 @@ TEST_F(RealModelE2ETest, RealModelCAbiEndToEnd) { sentences.push_back(line); } ASSERT_GT(sentences.size(), 1u) << "Public Profile corpus must not be empty"; - ASSERT_EQ(Alg_Init(), 0); - const std::string cfg_path = - (project_root_ / "configs/pipeline_entity_extract_cabi.json").string(); - const std::string model_root = model_root_.string(); + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + ASSERT_EQ(op.Init(), 0); - CompanyAlgParamCreate create_param; - create_param.config_file_path = cfg_path.c_str(); - create_param.model_root_dir = model_root.c_str(); + const std::string root_str = project_root_.string(); + operator_api::CreateParam create_param{}; + create_param.model_path = root_str.c_str(); + create_param.cfg_file_name = "configs/pipeline_entity_extract_cpu.conf"; create_param.device_id = 0; + create_param.compute_platform = operator_api::ComputePlatform::kCpu; + create_param.max_frame_depth = 25; void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, &create_param), 0); + ASSERT_EQ(op.Create(&handle, &create_param), 0); ASSERT_NE(handle, nullptr); for (size_t i = 0; i < sentences.size(); ++i) { SCOPED_TRACE(sentences[i]); const uint64_t request_id = i == 0 ? 99001 : 30000 + i; - CompanyEntityInputStruct req{request_id, sentences[i].c_str()}; - std::vector inputs = {&req}; - CompanyEntityOutputStruct out{}; - std::vector outputs = {&out}; - const int ret = Alg_Process(handle, inputs, outputs); + CompanyString cs{static_cast(sentences[i].size()), + const_cast(sentences[i].data())}; + CompanyOperatorEntityInput req{request_id, &cs}; + + operator_api::NamedIoBatch inputs(1); + inputs[0]["nlp_node.entity_in"] = + operator_api::MakeBorrowedOperatorInput(&req); + operator_api::NamedIoBatch outputs(1); + outputs[0]["nlp_node.entity_out"] = nullptr; + + const int ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); if (ret != 0) continue; - EXPECT_EQ(out.request_id, request_id); - EXPECT_EQ(out.status_code, 0); - const auto entities = - nlohmann::json::parse(out.entities_json, nullptr, false); - EXPECT_TRUE(entities.is_array()) << out.entities_json; - EXPECT_FALSE(entities.empty()) << out.entities_json; - std::cout << " [C ABI Real Model Output] " << out.entities_json - << std::endl; + ASSERT_EQ(outputs.size(), 1u); + auto out_sp = outputs[0]["nlp_node.entity_out"]; + ASSERT_NE(out_sp, nullptr); + auto* out = static_cast(out_sp.get()); + EXPECT_EQ(out->request_id, request_id); + EXPECT_EQ(out->status_code, 0); + ASSERT_NE(out->entities_json, nullptr); + std::string json_str(out->entities_json->data, out->entities_json->length); + const auto entities = nlohmann::json::parse(json_str, nullptr, false); + EXPECT_TRUE(entities.is_array()) << json_str; + EXPECT_FALSE(entities.empty()) << json_str; + std::cout << " [Operator Real Model Output] " << json_str << std::endl; } - EXPECT_EQ(Alg_Destroy(handle), 0); - EXPECT_EQ(Alg_DeInit(), 0); + EXPECT_EQ(op.Destroy(handle), 0); + EXPECT_EQ(op.Deinit(), 0); } #ifdef HAVE_WHISPERCPP diff --git a/tests/integration/operator/test_operator_api.cpp b/tests/integration/operator/test_operator_api.cpp index 99b6c50a..ab91b719 100644 --- a/tests/integration/operator/test_operator_api.cpp +++ b/tests/integration/operator/test_operator_api.cpp @@ -20,7 +20,6 @@ #include "adapter/operator/operator_value_type_registry.h" #include "core/common_contracts.h" #include "core/pipeline_catalog.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/interface.h" #include "edgeflow/operator/types.h" #include "engine/backend_registry.h" @@ -2708,22 +2707,21 @@ TEST_F(OperatorApiTest, SharedCarrierDoesNotMergePayloadSchema) { // "query" const auto* translate_in_conv = llm_edgeflow::IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(translate_in_conv, nullptr); - CompanyEntityInputStruct c_in_plain{50001, plain_text.c_str()}; - const void* translate_inputs[] = {&c_in_plain}; + CompanyOperatorEntityInput c_in_plain{50001, &cs_plain}; llm_edgeflow::AlgContext ctx; llm_edgeflow::AdapterStatus status; llm_edgeflow::ExternalInputBatchView view_plain; - view_plain.items = translate_inputs; view_plain.count = 1; - view_plain.type_id = translate_in_conv->external_type; + view_plain.leased_slots["entity_in"] = {&c_in_plain}; + view_plain.slot_types["entity_in"] = "CompanyOperatorEntityInput"; llm_edgeflow::InputPortBindings port_bindings( {{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); llm_edgeflow::InputDecodeOptions decode_opts; decode_opts.converter_id = translate_in_conv->converter_id; - decode_opts.transport = "cabi"; + decode_opts.transport = "operator"; decode_opts.max_batch_size = 64; EXPECT_EQ(translate_in_conv->decode_fn(view_plain, decode_opts, port_bindings, &ctx, &status), @@ -2731,13 +2729,14 @@ TEST_F(OperatorApiTest, SharedCarrierDoesNotMergePayloadSchema) { // 2. JSON text: Translate accepts and extracts "query" std::string json_text = "{\"query\":\"有效翻译查询\"}"; - CompanyEntityInputStruct c_in_json{50002, json_text.c_str()}; - const void* translate_valid_inputs[] = {&c_in_json}; + CompanyString cs_json{static_cast(json_text.size()), + const_cast(json_text.data())}; + CompanyOperatorEntityInput c_in_json{50002, &cs_json}; llm_edgeflow::AlgContext valid_ctx; llm_edgeflow::ExternalInputBatchView view_json; - view_json.items = translate_valid_inputs; view_json.count = 1; - view_json.type_id = translate_in_conv->external_type; + view_json.leased_slots["entity_in"] = {&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); diff --git a/tests/integration/operator/test_operator_golden.cpp b/tests/integration/operator/test_operator_golden.cpp index cf589cb2..d74360c1 100644 --- a/tests/integration/operator/test_operator_golden.cpp +++ b/tests/integration/operator/test_operator_golden.cpp @@ -7,7 +7,6 @@ #include #include -#include "edgeflow/c_api.h" #include "edgeflow/operator/interface.h" #include "engine/backend_registry.h" diff --git a/tests/integration/pipeline/test_doc_qa_rerank.cpp b/tests/integration/pipeline/test_doc_qa_rerank.cpp index a507cb69..2560867b 100644 --- a/tests/integration/pipeline/test_doc_qa_rerank.cpp +++ b/tests/integration/pipeline/test_doc_qa_rerank.cpp @@ -5,7 +5,6 @@ #include #include -#include "edgeflow/c_api.h" #include "edgeflow/operator/interface.h" #include "edgeflow/operator/types.h" diff --git a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp index 3054ffaa..a1e6edae 100644 --- a/tests/integration/pipeline/test_pipeline_catalog_validator.cpp +++ b/tests/integration/pipeline/test_pipeline_catalog_validator.cpp @@ -7,6 +7,7 @@ #include #include "adapter/io_binding_registry.h" +#include "adapter/io_binding_resolver.h" #include "adapter/shared_algorithm_runtime.h" #include "core/common_contracts.h" #include "core/node_registry.h" @@ -95,8 +96,7 @@ TEST(PipelineValidatorTest, AllRepositoryPipelinesValidate) { for (const auto& entry : std::filesystem::directory_iterator(configs)) { const auto filename = entry.path().filename().string(); if (!entry.is_regular_file() || entry.path().extension() != ".json" || - filename.rfind("pipeline_", 0) != 0 || - filename.find("_cabi.json") != std::string::npos) { + filename.rfind("pipeline_", 0) != 0) { continue; } ++candidates; @@ -265,23 +265,23 @@ TEST(PipelineValidatorTest, TableDrivenParityMatrix) { std::string biz = config.value("biz_name", ""); std::string binding_id; for (const auto& b : IoBindingRegistry::Instance().AllBindings()) { - if (b.biz_name == biz && b.transport == "cabi") { + if (b.biz_name == biz && b.transport == "operator") { binding_id = b.binding_id; break; } } if (binding_id.empty()) { - binding_id = "keyword_match.cabi.v1"; + binding_id = "keyword_match.operator.v1"; } - std::unique_ptr runtime; - std::string runtime_error; - int runtime_result = SharedAlgorithmRuntime::CreateFromPipelineJson( - config, 0, "./models", binding_id, &runtime, &runtime_error); - EXPECT_EQ(runtime_result, test["runtime_error_code"].get()); - EXPECT_EQ(runtime, nullptr); - EXPECT_NE(runtime_error.find(test["primary_code"].get()), + std::unique_ptr io_plan; + std::string resolve_error; + int resolve_result = IoBindingResolver::ResolveFromPipelineJson( + config, binding_id, "operator", "./models", &io_plan, &resolve_error); + EXPECT_NE(resolve_result, 0); + EXPECT_EQ(io_plan, nullptr); + EXPECT_NE(resolve_error.find(test["primary_code"].get()), std::string::npos); - EXPECT_NE(runtime_error.find(test["primary_path"].get()), + EXPECT_NE(resolve_error.find(test["primary_path"].get()), std::string::npos); } } diff --git a/tests/integration/runtime/test_all_biz_pipelines.cpp b/tests/integration/runtime/test_all_biz_pipelines.cpp index eaba958b..e24d8765 100644 --- a/tests/integration/runtime/test_all_biz_pipelines.cpp +++ b/tests/integration/runtime/test_all_biz_pipelines.cpp @@ -2,46 +2,44 @@ #include #include +#include #include #include #include -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" - -static std::string GetConfigPath(const std::string& rel_path) { - FILE* fp = fopen(rel_path.c_str(), "r"); - if (fp) { - fclose(fp); - return rel_path; - } - return "../" + rel_path; -} +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { class AllBizPipelinesTest : public ::testing::Test { protected: - void SetUp() override { Alg_Init(); } - void TearDown() override { Alg_DeInit(); } + void SetUp() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } }; // 1. 业务 3 (智能长文档切片问答 RAG) 细粒度断言测试 (DocChunk -> Embedding -> // VectorSearch -> Prompt -> LLM) TEST_F(AllBizPipelinesTest, DocQaPipelineExecution) { - std::string cfg_path = - GetConfigPath("demo/fixtures/mock/pipeline_doc_qa_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "demo/fixtures/mock/pipeline_doc_qa.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); ASSERT_NE(handle, nullptr); - const char* doc_text = + std::string doc_text = "第一章 " "平台注册规范:用户须使用真实身份信息注册,禁止恶意注册多个账号。\n" "第二章 " @@ -57,89 +55,160 @@ TEST_F(AllBizPipelinesTest, DocQaPipelineExecution) { "违禁品管控规则:严禁在平台发布、宣传、交易任何国家法律法规禁止流通的违禁" "商品。"; - CompanyDocInputStruct req0{30001, doc_text, - "请问平台支持7天无理由退款吗?具体要求是什么?"}; - CompanyDocInputStruct req1{30002, doc_text, "跨境信用卡支付要收手续费吗?"}; - std::vector inputs = {&req0, &req1}; + std::string q0 = "请问平台支持7天无理由退款吗?具体要求是什么?"; + std::string q1 = "跨境信用卡支付要收手续费吗?"; + + CompanyString cs_doc{static_cast(doc_text.size()), + const_cast(doc_text.data())}; + CompanyString cs_q0{static_cast(q0.size()), + const_cast(q0.data())}; + CompanyString cs_q1{static_cast(q1.size()), + const_cast(q1.data())}; + + CompanyOperatorDocInput req0{30001, &cs_doc, &cs_q0}; + CompanyOperatorDocInput req1{30002, &cs_doc, &cs_q1}; + + operator_api::NamedIoBatch inputs(2); + inputs[0]["rag_channel.doc_in"] = + operator_api::MakeBorrowedOperatorInput(&req0); + inputs[1]["rag_channel.doc_in"] = + operator_api::MakeBorrowedOperatorInput(&req1); - CompanyDocOutputStruct out0; - CompanyDocOutputStruct out1; - std::vector outputs = {&out0, &out1}; + operator_api::NamedIoBatch outputs(2); + outputs[0]["rag_channel.doc_out"] = nullptr; + outputs[1]["rag_channel.doc_out"] = nullptr; - ret = Alg_Process(handle, inputs, outputs); + ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); + ASSERT_EQ(outputs.size(), 2u); - // 验证切片数与意图分类 - EXPECT_EQ(out0.request_id, 30001ULL); - EXPECT_GT(out0.chunk_count, 0); - EXPECT_STREQ(out0.intent_name, "AFTER_SALES_REFUND"); - EXPECT_TRUE(strlen(out0.answer_text) > 0); + auto out0_sp = outputs[0]["rag_channel.doc_out"]; + auto out1_sp = outputs[1]["rag_channel.doc_out"]; + ASSERT_NE(out0_sp, nullptr); + ASSERT_NE(out1_sp, nullptr); - EXPECT_EQ(out1.request_id, 30002ULL); - EXPECT_GT(out1.chunk_count, 0); - EXPECT_TRUE(strlen(out1.answer_text) > 0); + auto* out0 = static_cast(out0_sp.get()); + auto* out1 = static_cast(out1_sp.get()); - ret = Alg_Destroy(handle); + // 验证切片数与意图分类 + EXPECT_EQ(out0->request_id, 30001ULL); + EXPECT_GT(out0->chunk_count, 0); + ASSERT_NE(out0->intent_name, nullptr); + EXPECT_EQ(std::string(out0->intent_name->data, out0->intent_name->length), + "AFTER_SALES_REFUND"); + ASSERT_NE(out0->answer_text, nullptr); + EXPECT_GT(out0->answer_text->length, 0); + + EXPECT_EQ(out1->request_id, 30002ULL); + EXPECT_GT(out1->chunk_count, 0); + ASSERT_NE(out1->answer_text, nullptr); + out0_sp.reset(); + out1_sp.reset(); + outputs.clear(); + + ret = op.Destroy(handle); EXPECT_EQ(ret, 0); } // 2. 业务 4 (智能对话风控质检 - 3模型6节点级联) 细粒度高危与合规样本双向校验 TEST_F(AllBizPipelinesTest, DialogueComplianceAuditPipeline) { - std::string cfg_path = - GetConfigPath("demo/fixtures/mock/pipeline_dialogue_audit_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "demo/fixtures/mock/pipeline_dialogue_audit.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); ASSERT_NE(handle, nullptr); // 样本 A: 违规诱导私下交易 - CompanyAuditInputStruct req_violation{ - 40001, + std::string s_viol = "亲,平台退款审核太慢了,你加我私人微信转账给我吧,我私下把商品寄给你,还" - "能返现20元!", - "VIP专席客服"}; + "能返现20元!"; + std::string c_viol = "VIP专席客服"; // 样本 B: 合规正常客服沟通 - CompanyAuditInputStruct req_safe{40002, - "您好,您的商品符合7天无理由退货政策,已为您" - "在系统提交退款换货流程,请保持手机畅通。", - "在线售后IM"}; + std::string s_safe = + "您好,您的商品符合7天无理由退货政策,已为您" + "在系统提交退款换货流程,请保持手机畅通。"; + std::string c_safe = "在线售后IM"; + + CompanyString cs_viol{static_cast(s_viol.size()), + const_cast(s_viol.data())}; + CompanyString cc_viol{static_cast(c_viol.size()), + const_cast(c_viol.data())}; + + CompanyString cs_safe{static_cast(s_safe.size()), + const_cast(s_safe.data())}; + CompanyString cc_safe{static_cast(c_safe.size()), + const_cast(c_safe.data())}; + + CompanyOperatorAuditInput req_violation{40001, &cs_viol, &cc_viol}; + CompanyOperatorAuditInput req_safe{40002, &cs_safe, &cc_safe}; + + operator_api::NamedIoBatch inputs(2); + inputs[0]["audit_channel.audit_in"] = + operator_api::MakeBorrowedOperatorInput(&req_violation); + inputs[1]["audit_channel.audit_in"] = + operator_api::MakeBorrowedOperatorInput(&req_safe); + + operator_api::NamedIoBatch outputs(2); + outputs[0]["audit_channel.audit_out"] = nullptr; + outputs[1]["audit_channel.audit_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); + EXPECT_EQ(ret, 0); + ASSERT_EQ(outputs.size(), 2u); - std::vector inputs = {&req_violation, &req_safe}; + auto out_viol_sp = outputs[0]["audit_channel.audit_out"]; + auto out_safe_sp = outputs[1]["audit_channel.audit_out"]; + ASSERT_NE(out_viol_sp, nullptr); + ASSERT_NE(out_safe_sp, nullptr); - CompanyAuditOutputStruct out_violation; - CompanyAuditOutputStruct out_safe; - std::vector outputs = {&out_violation, &out_safe}; - - ret = Alg_Process(handle, inputs, outputs); - EXPECT_EQ(ret, 0); + auto* out_violation = + static_cast(out_viol_sp.get()); + auto* out_safe = static_cast(out_safe_sp.get()); // 验证样本 A 判定为 HIGH_RISK,且命中对应合规条款 - EXPECT_EQ(out_violation.request_id, 40001); - EXPECT_STREQ(out_violation.risk_level, "HIGH_RISK"); - EXPECT_GE(out_violation.risk_score, 0.80f); - EXPECT_TRUE(std::string(out_violation.matched_policy_clause).find("退货") != - std::string::npos || - std::string(out_violation.matched_policy_clause).find("条款") != - std::string::npos); - - auto j_violation = nlohmann::json::parse(out_violation.audit_verdict_json); + EXPECT_EQ(out_violation->request_id, 40001ULL); + ASSERT_NE(out_violation->risk_level, nullptr); + EXPECT_EQ(std::string(out_violation->risk_level->data, + out_violation->risk_level->length), + "HIGH_RISK"); + EXPECT_GE(out_violation->risk_score, 0.80f); + ASSERT_NE(out_violation->matched_policy_clause, nullptr); + std::string matched(out_violation->matched_policy_clause->data, + out_violation->matched_policy_clause->length); + EXPECT_TRUE(matched.find("退货") != std::string::npos || + matched.find("条款") != std::string::npos); + + ASSERT_NE(out_violation->audit_verdict_json, nullptr); + std::string json_viol(out_violation->audit_verdict_json->data, + out_violation->audit_verdict_json->length); + auto j_violation = nlohmann::json::parse(json_viol); EXPECT_EQ(j_violation["risk_level"], "HIGH_RISK"); // 验证样本 B 判定为 SAFE - EXPECT_EQ(out_safe.request_id, 40002); - EXPECT_STREQ(out_safe.risk_level, "SAFE"); - EXPECT_LE(out_safe.risk_score, 0.40f); - - auto j_safe = nlohmann::json::parse(out_safe.audit_verdict_json); - EXPECT_EQ(j_safe["risk_level"], "SAFE"); - - ret = Alg_Destroy(handle); + EXPECT_EQ(out_safe->request_id, 40002ULL); + ASSERT_NE(out_safe->risk_level, nullptr); + EXPECT_EQ( + std::string(out_safe->risk_level->data, out_safe->risk_level->length), + "SAFE"); + EXPECT_LE(out_safe->risk_score, 0.40f); + + ASSERT_NE(out_safe->audit_verdict_json, nullptr); + std::string json_safe(out_safe->audit_verdict_json->data, + out_safe->audit_verdict_json->length); + auto j_safe = nlohmann::json::parse(json_safe); + out_viol_sp.reset(); + out_safe_sp.reset(); + outputs.clear(); + + ret = op.Destroy(handle); EXPECT_EQ(ret, 0); } diff --git a/tests/integration/runtime/test_concurrency_and_edge_cases.cpp b/tests/integration/runtime/test_concurrency_and_edge_cases.cpp index 7e81a79b..96bb46c4 100644 --- a/tests/integration/runtime/test_concurrency_and_edge_cases.cpp +++ b/tests/integration/runtime/test_concurrency_and_edge_cases.cpp @@ -3,29 +3,26 @@ #include #include #include +#include #include #include #include #include -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" - -static std::string GetConfigPath(const std::string& rel_path) { - FILE* fp = fopen(rel_path.c_str(), "r"); - if (fp) { - fclose(fp); - return rel_path; - } - return "../" + rel_path; -} +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { class ConcurrencyAndEdgeCasesTest : public ::testing::Test { protected: - void SetUp() override { Alg_Init(); } - void TearDown() override { Alg_DeInit(); } + void SetUp() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } }; // 1. 多线程高并发句柄独立运行与竞争测试 (8 个 Worker 线程并发 20 轮全生命周期) @@ -38,49 +35,65 @@ TEST_F(ConcurrencyAndEdgeCasesTest, MultiThreadedConcurrentStressTest) { std::vector workers; workers.reserve(num_threads); - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); for (int t = 0; t < num_threads; ++t) { workers.emplace_back([&, t]() { - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; for (int iter = 0; iter < iterations_per_thread; ++iter) { void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); if (ret != 0 || !handle) { error_count++; continue; } // 动态下发规则 - CompanyAlgParamControl ctrl; - ctrl.control_cmd = 1; std::string rule_json = "{\"categories\": {\"THREAD_VIP_" + std::to_string(t) + "\": [\"VIP" + std::to_string(t) + "\"]}}"; - ctrl.json_param_str = rule_json.c_str(); - Alg_Control(handle, &ctrl); + operator_api::ControlUpdateRulesParam ctrl{rule_json.c_str()}; + op.Control(handle, operator_api::ControlCommand::kUpdateRules, &ctrl); // 执行推理 std::string query = "客户请求VIP" + std::to_string(t) + "专席服务"; - CompanyKeywordInputStruct in_req{static_cast(t * 1000 + iter), - query.c_str()}; - std::vector inputs = {&in_req}; - CompanyKeywordOutputStruct out_res; - std::vector outputs = {&out_res}; - - ret = Alg_Process(handle, inputs, outputs); - if (ret == 0 && out_res.is_hit == 1) { - success_count++; + CompanyString cs{static_cast(query.size()), + const_cast(query.data())}; + CompanyOperatorKeywordInput in_req{ + static_cast(t * 1000 + iter), &cs}; + + operator_api::NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req); + operator_api::NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); + if (ret == 0 && !outputs.empty()) { + auto out_sp = outputs[0]["client_channel.keyword_out"]; + if (out_sp) { + auto* out_res = + static_cast(out_sp.get()); + if (out_res && out_res->is_hit == 1) { + success_count++; + } else { + error_count++; + } + } else { + error_count++; + } } else { error_count++; } - ret = Alg_Destroy(handle); + outputs.clear(); + ret = op.Destroy(handle); if (ret != 0) { error_count++; } @@ -98,90 +111,123 @@ TEST_F(ConcurrencyAndEdgeCasesTest, MultiThreadedConcurrentStressTest) { // 2. 极端边界与畸形数据鲁棒性测试 (Edge Cases & Fault Tolerance) TEST_F(ConcurrencyAndEdgeCasesTest, EdgeCasesAndFaultTolerance) { - // Case A: 畸形与非法 JSON 传入 Alg_Control + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + + // Case A: 畸形与非法 JSON 传入 Control { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); - CompanyAlgParamControl ctrl_invalid; - ctrl_invalid.control_cmd = 1; - ctrl_invalid.json_param_str = "{invalid_malformed_json..."; // 畸形 JSON - ret = Alg_Control(handle, &ctrl_invalid); + operator_api::ControlUpdateRulesParam ctrl_invalid; + ctrl_invalid.rules_json_str = "{invalid_malformed_json..."; // 畸形 JSON + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, + &ctrl_invalid); // 框架应安全拦截并返回错误码,决不能崩溃 EXPECT_NE(ret, 0); // 传入空字符串 - ctrl_invalid.json_param_str = ""; - ret = Alg_Control(handle, &ctrl_invalid); + ctrl_invalid.rules_json_str = ""; + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, + &ctrl_invalid); EXPECT_NE(ret, 0); // 传入空指针 - ctrl_invalid.json_param_str = nullptr; - ret = Alg_Control(handle, &ctrl_invalid); + ctrl_invalid.rules_json_str = nullptr; + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, + &ctrl_invalid); EXPECT_NE(ret, 0); - Alg_Destroy(handle); + op.Destroy(handle); } // Case B: 空文本与纯标点符号输入 { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); + ASSERT_EQ(ret, 0); - CompanyKeywordInputStruct empty_req{99901, ""}; // 空文本 - CompanyKeywordInputStruct symbols_req{99902, - " !@#$%^&*()_+~`|}{[]:;?><,./ "}; - std::vector inputs = {&empty_req, &symbols_req}; + std::string empty_str = ""; + std::string symbols_str = " !@#$%^&*()_+~`|}{[]:;?><,./ "; + CompanyString cs_empty{static_cast(empty_str.size()), + const_cast(empty_str.data())}; + CompanyString cs_symbols{static_cast(symbols_str.size()), + const_cast(symbols_str.data())}; - CompanyKeywordOutputStruct out0; - CompanyKeywordOutputStruct out1; - std::vector outputs = {&out0, &out1}; + CompanyOperatorKeywordInput empty_req{99901, &cs_empty}; + CompanyOperatorKeywordInput symbols_req{99902, &cs_symbols}; - int ret = Alg_Process(handle, inputs, outputs); + operator_api::NamedIoBatch inputs(2); + inputs[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&empty_req); + inputs[1]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&symbols_req); + + operator_api::NamedIoBatch outputs(2); + outputs[0]["client_channel.keyword_out"] = nullptr; + outputs[1]["client_channel.keyword_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); - EXPECT_EQ(out0.is_hit, 0); - EXPECT_EQ(out1.is_hit, 0); + ASSERT_EQ(outputs.size(), 2u); + + auto out0_sp = outputs[0]["client_channel.keyword_out"]; + auto out1_sp = outputs[1]["client_channel.keyword_out"]; + ASSERT_NE(out0_sp, nullptr); + ASSERT_NE(out1_sp, nullptr); - Alg_Destroy(handle); + auto* out0 = static_cast(out0_sp.get()); + auto* out1 = static_cast(out1_sp.get()); + + EXPECT_EQ(out0->is_hit, 0); + EXPECT_EQ(out1->is_hit, 0); + + out0_sp.reset(); + out1_sp.reset(); + outputs.clear(); + op.Destroy(handle); } // Case C: 音频 0 采样点边界 { - std::string cfg_path = - GetConfigPath("demo/fixtures/mock/pipeline_audio_asr_intent_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "demo/fixtures/mock/pipeline_audio_asr_intent.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); - CompanyAudioInputStruct empty_audio{99903, nullptr, 0, 16000}; - std::vector inputs = {&empty_audio}; - CompanyAudioOutputStruct out_audio; - std::vector outputs = {&out_audio}; + CompanyOperatorAudioInput empty_audio{99903, nullptr, 0, 16000}; + operator_api::NamedIoBatch inputs(1); + inputs[0]["mic_0.audio_in"] = + operator_api::MakeBorrowedOperatorInput(&empty_audio); + + operator_api::NamedIoBatch outputs(1); + outputs[0]["mic_0.audio_out"] = nullptr; - ret = Alg_Process(handle, inputs, outputs); + ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); - Alg_Destroy(handle); + outputs.clear(); + op.Destroy(handle); } } diff --git a/tests/integration/runtime/test_different_io_modalities.cpp b/tests/integration/runtime/test_different_io_modalities.cpp index ed9ab216..7b4b26fa 100644 --- a/tests/integration/runtime/test_different_io_modalities.cpp +++ b/tests/integration/runtime/test_different_io_modalities.cpp @@ -10,9 +10,10 @@ #include #include -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" #include "engine/backend_registry.h" +#include "platform_mock/operator_data_types.h" static std::string GetConfigPath(const std::string& rel_path) { FILE* fp = fopen(rel_path.c_str(), "r"); @@ -27,60 +28,102 @@ namespace llm_edgeflow { class DifferentIoModalitiesTest : public ::testing::Test { protected: - void SetUp() override { Alg_Init(); } - void TearDown() override { Alg_DeInit(); } + void SetUp() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } }; // 1. 验证业务 5: 多模态图文票据问答 (Image + Query -> OCR BBox -> LLM JSON) TEST_F(DifferentIoModalitiesTest, OcrDocQa) { - std::string cfg_path = - GetConfigPath("demo/fixtures/mock/pipeline_ocr_doc_qa_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "demo/fixtures/mock/pipeline_ocr_doc_qa.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); ASSERT_NE(handle, nullptr); - CompanyOcrDocInputStruct in_req1{60001, "./data/invoice_sample_01.jpg", - "提取发票代码、号码与总金额"}; - CompanyOcrDocInputStruct in_req2{60002, "./data/vat_receipt_02.png", - "提取购买方公司名称与税额"}; - std::vector inputs = {&in_req1, &in_req2}; + std::string img1 = "./data/invoice_sample_01.jpg"; + std::string p1 = "提取发票代码、号码与总金额"; + std::string img2 = "./data/vat_receipt_02.png"; + std::string p2 = "提取购买方公司名称与税额"; + + CompanyString img1_cs{static_cast(img1.size()), + const_cast(img1.data())}; + CompanyFrame frame1{60001, &img1_cs, nullptr}; + CompanyString p1_cs{static_cast(p1.size()), + const_cast(p1.data())}; + + CompanyString img2_cs{static_cast(img2.size()), + const_cast(img2.data())}; + CompanyFrame frame2{60002, &img2_cs, nullptr}; + CompanyString p2_cs{static_cast(p2.size()), + const_cast(p2.data())}; + + operator_api::NamedIoBatch inputs(2); + inputs[0]["camera_0.frame"] = + operator_api::MakeBorrowedOperatorInput(&frame1); + inputs[0]["camera_0.string"] = + operator_api::MakeBorrowedOperatorInput(&p1_cs); + inputs[1]["camera_0.frame"] = + operator_api::MakeBorrowedOperatorInput(&frame2); + inputs[1]["camera_0.string"] = + operator_api::MakeBorrowedOperatorInput(&p2_cs); + + operator_api::NamedIoBatch outputs(2); + outputs[0]["camera_0.od_out"] = nullptr; + outputs[1]["camera_0.od_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); + EXPECT_EQ(ret, 0); + ASSERT_EQ(outputs.size(), 2u); - CompanyOcrDocOutputStruct out1; - CompanyOcrDocOutputStruct out2; - std::vector outputs = {&out1, &out2}; + auto out1_sp = outputs[0]["camera_0.od_out"]; + auto out2_sp = outputs[1]["camera_0.od_out"]; + ASSERT_NE(out1_sp, nullptr); + ASSERT_NE(out2_sp, nullptr); - ret = Alg_Process(handle, inputs, outputs); - EXPECT_EQ(ret, 0); - EXPECT_EQ(out1.request_id, 60001ULL); - EXPECT_EQ(out1.detected_box_count, 6U); - EXPECT_EQ(out2.request_id, 60002ULL); - EXPECT_EQ(out2.detected_box_count, 6U); + auto* out1 = static_cast(out1_sp.get()); + auto* out2 = static_cast(out2_sp.get()); + + EXPECT_EQ(out1->request_id, 60001ULL); + EXPECT_EQ(out1->detected_box_count, 6); + EXPECT_EQ(out2->request_id, 60002ULL); + EXPECT_EQ(out2->detected_box_count, 6); - auto j1 = nlohmann::json::parse(out1.extracted_invoice_json); + ASSERT_NE(out1->result_json, nullptr); + std::string json_str1(out1->result_json->data, out1->result_json->length); + auto j1 = nlohmann::json::parse(json_str1); EXPECT_TRUE(j1.contains("invoice_code") && j1.contains("total_amount")); - ret = Alg_Destroy(handle); + out1_sp.reset(); + out2_sp.reset(); + outputs.clear(); + ret = op.Destroy(handle); EXPECT_EQ(ret, 0); } // 2. 验证业务 6: 语音识别与时序意图槽位抽取 (Float PCM Buffer -> Speech Text -> // NLU Intent/Slots) TEST_F(DifferentIoModalitiesTest, AudioAsrIntent) { - std::string cfg_path = - GetConfigPath("demo/fixtures/mock/pipeline_audio_asr_intent_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "demo/fixtures/mock/pipeline_audio_asr_intent.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); ASSERT_NE(handle, nullptr); @@ -88,36 +131,72 @@ TEST_F(DifferentIoModalitiesTest, AudioAsrIntent) { std::vector pcm1(16000, 0.01f); // 导航语音 (累计值较大) std::vector pcm2(16000, 0.001f); // 空调车控语音 - CompanyAudioInputStruct in_audio1{70001, pcm1.data(), - static_cast(pcm1.size()), 16000}; - CompanyAudioInputStruct in_audio2{70002, pcm2.data(), - static_cast(pcm2.size()), 16000}; - std::vector inputs = {&in_audio1, &in_audio2}; + CompanyOperatorAudioInput in_audio1{70001, pcm1.data(), + static_cast(pcm1.size()), 16000}; + CompanyOperatorAudioInput in_audio2{70002, pcm2.data(), + static_cast(pcm2.size()), 16000}; - CompanyAudioOutputStruct out1; - CompanyAudioOutputStruct out2; - std::vector outputs = {&out1, &out2}; + operator_api::NamedIoBatch inputs(2); + inputs[0]["mic_0.audio_in"] = + operator_api::MakeBorrowedOperatorInput(&in_audio1); + inputs[1]["mic_0.audio_in"] = + operator_api::MakeBorrowedOperatorInput(&in_audio2); - ret = Alg_Process(handle, inputs, outputs); + operator_api::NamedIoBatch outputs(2); + outputs[0]["mic_0.audio_out"] = nullptr; + outputs[1]["mic_0.audio_out"] = nullptr; + + ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); - EXPECT_EQ(out1.request_id, 70001); - EXPECT_EQ(out2.request_id, 70002); + ASSERT_EQ(outputs.size(), 2u); + + auto out1_sp = outputs[0]["mic_0.audio_out"]; + auto out2_sp = outputs[1]["mic_0.audio_out"]; + ASSERT_NE(out1_sp, nullptr); + ASSERT_NE(out2_sp, nullptr); + + auto* out1 = static_cast(out1_sp.get()); + auto* out2 = static_cast(out2_sp.get()); + + EXPECT_EQ(out1->request_id, 70001ULL); + EXPECT_EQ(out2->request_id, 70002ULL); + + ASSERT_NE(out1->intent_slot_json, nullptr); + std::string s1(out1->intent_slot_json->data, out1->intent_slot_json->length); + auto j1 = nlohmann::json::parse(s1); + + ASSERT_NE(out2->intent_slot_json, nullptr); + std::string s2(out2->intent_slot_json->data, out2->intent_slot_json->length); + auto j2 = nlohmann::json::parse(s2); - auto j1 = nlohmann::json::parse(out1.intent_slot_json); - auto j2 = nlohmann::json::parse(out2.intent_slot_json); EXPECT_EQ(j1["intent"], "NAVIGATION"); EXPECT_EQ(j2["intent"], "VEHICLE_HVAC_CONTROL"); - in_audio1.pcm_length = 0; - in_audio1.pcm_buffer = nullptr; - in_audio2.pcm_length = 0; - EXPECT_EQ(Alg_Process(handle, inputs, outputs), 0); - EXPECT_EQ(out1.request_id, 70001U); - EXPECT_EQ(out2.request_id, 70002U); - EXPECT_EQ(out1.status_code, 0); - EXPECT_EQ(out2.status_code, 0); - - ret = Alg_Destroy(handle); + CompanyOperatorAudioInput empty1{70001, nullptr, 0, 16000}; + CompanyOperatorAudioInput empty2{70002, nullptr, 0, 16000}; + inputs[0]["mic_0.audio_in"] = + operator_api::MakeBorrowedOperatorInput(&empty1); + inputs[1]["mic_0.audio_in"] = + operator_api::MakeBorrowedOperatorInput(&empty2); + outputs[0]["mic_0.audio_out"] = nullptr; + outputs[1]["mic_0.audio_out"] = nullptr; + + EXPECT_EQ(op.Process(handle, inputs, outputs), 0); + out1_sp = outputs[0]["mic_0.audio_out"]; + out2_sp = outputs[1]["mic_0.audio_out"]; + ASSERT_NE(out1_sp, nullptr); + ASSERT_NE(out2_sp, nullptr); + out1 = static_cast(out1_sp.get()); + out2 = static_cast(out2_sp.get()); + EXPECT_EQ(out1->request_id, 70001ULL); + EXPECT_EQ(out2->request_id, 70002ULL); + EXPECT_EQ(out1->status_code, 0); + EXPECT_EQ(out2->status_code, 0); + + out1_sp.reset(); + out2_sp.reset(); + outputs.clear(); + ret = op.Destroy(handle); EXPECT_EQ(ret, 0); } @@ -139,74 +218,112 @@ TEST_F(DifferentIoModalitiesTest, CrossRerankBatch) { ASSERT_TRUE(json_in.good()); nlohmann::json pipe_json; json_in >> pipe_json; - pipe_json["models"][0]["model_path"] = EDGEFLOW_RERANK_ONNX_FIXTURE; - pipe_json["models"][0]["model_config"]["tokenizer_file"] = - EDGEFLOW_VOCAB_FIXTURE; - pipe_json["models"][0]["model_config"]["max_length"] = 32; - auto temp_dir = std::filesystem::temp_directory_path() / ("test_different_io_rerank_" + std::to_string(rand())); - std::filesystem::create_directories(temp_dir); + auto models_dir = temp_dir / "models"; + std::filesystem::create_directories(models_dir); + std::error_code copy_ec; + std::filesystem::copy_file( + EDGEFLOW_RERANK_ONNX_FIXTURE, models_dir / "rerank.onnx", + std::filesystem::copy_options::overwrite_existing, copy_ec); + std::filesystem::copy_file(EDGEFLOW_VOCAB_FIXTURE, models_dir / "vocab.txt", + std::filesystem::copy_options::overwrite_existing, + copy_ec); + + pipe_json["models"][0]["model_path"] = "models/rerank.onnx"; + pipe_json["models"][0]["model_config"]["tokenizer_file"] = "vocab.txt"; + pipe_json["models"][0]["model_config"]["max_length"] = 32; + auto temp_pipe_path = temp_dir / "pipeline_cross_rerank.json"; std::ofstream json_out(temp_pipe_path); json_out << pipe_json.dump(2); json_out.close(); - nlohmann::json deploy_cfg = {{"schema_version", 1}, - {"data", - {{"pipe_path", "pipeline_cross_rerank.json"}, - {"io_binding", "cross_rerank.cabi.v1"}}}}; - auto temp_cfg_path = temp_dir / "pipeline_cross_rerank_cabi.json"; + nlohmann::json deploy_cfg = { + {"schema_version", 1}, + {"data", + {{"pipe_path", "pipeline_cross_rerank.json"}, + {"io_binding", "cross_rerank.operator.v1"}, + {"outputs", + {{"rerank_out", + {{"type", "rerank_out"}, + {"meta_num", 0}, + {"metadata_type_id", 0}, + {"capacities", nlohmann::json::object()}}}}}}}}; + auto temp_cfg_path = temp_dir / "pipeline_cross_rerank.conf"; std::ofstream cfg_out(temp_cfg_path); cfg_out << deploy_cfg.dump(2); cfg_out.close(); - std::string temp_cfg_str = temp_cfg_path.string(); - CompanyAlgParamCreate param; - param.config_file_path = temp_cfg_str.c_str(); - param.model_root_dir = ""; - param.device_id = 0; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = temp_dir.c_str(); + param.cfg_file_name = "pipeline_cross_rerank.conf"; + param.device_id = 1; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - param.device_id = 1; - int ret = Alg_Create(&handle, ¶m); + int ret = op.Create(&handle, ¶m); EXPECT_NE(ret, 0); EXPECT_EQ(handle, nullptr); param.device_id = 0; - ret = Alg_Create(&handle, ¶m); + ret = op.Create(&handle, ¶m); ASSERT_EQ(ret, 0); ASSERT_NE(handle, nullptr); - const char* candidates[5] = { + std::string query = "请问如何申请7天无理由退款?"; + std::vector candidates = { "条款A: 仅在工作日提供人工客服支持。", "条款B: 支持7天无理由退货政策,审核通过后即时原路返还资金。", "条款C: 境外信用卡交易收取3%跨境手续费。", "条款D: 电子发票在订单完成后24小时内发送至邮箱。", "条款E: VIP用户享受专属1对1客服通道与快速理赔。"}; - CompanyRerankBatchInputStruct in_rerank; + CompanyString q_cs{static_cast(query.size()), + const_cast(query.data())}; + std::vector c_cs; + c_cs.reserve(5); + for (int i = 0; i < 5; ++i) { + c_cs.push_back({static_cast(candidates[i].size()), + const_cast(candidates[i].data())}); + } + + CompanyOperatorRerankInput in_rerank{}; in_rerank.request_id = 80001; - in_rerank.query_text = "请问如何申请7天无理由退款?"; + in_rerank.query_text = &q_cs; in_rerank.candidate_count = 5; - for (int i = 0; i < 5; ++i) in_rerank.candidate_passages[i] = candidates[i]; + for (int i = 0; i < 5; ++i) { + in_rerank.candidate_passages[i] = &c_cs[i]; + } - std::vector inputs = {&in_rerank}; - CompanyRerankBatchOutputStruct out_rerank; - std::vector outputs = {&out_rerank}; + operator_api::NamedIoBatch inputs(1); + inputs[0]["ranker.rerank_in"] = + operator_api::MakeBorrowedOperatorInput(&in_rerank); + operator_api::NamedIoBatch outputs(1); + outputs[0]["ranker.rerank_out"] = nullptr; - ret = Alg_Process(handle, inputs, outputs); + ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); - EXPECT_EQ(out_rerank.request_id, 80001); - EXPECT_EQ(out_rerank.count, 5); + ASSERT_EQ(outputs.size(), 1u); + + auto out_sp = outputs[0]["ranker.rerank_out"]; + ASSERT_NE(out_sp, nullptr); + auto* out_rerank = static_cast(out_sp.get()); + + EXPECT_EQ(out_rerank->request_id, 80001ULL); + EXPECT_EQ(out_rerank->count, 5); - for (int i = 0; i < out_rerank.count; ++i) { + for (int i = 0; i < out_rerank->count; ++i) { if (i > 0) { - EXPECT_GE(out_rerank.scores[i - 1], out_rerank.scores[i]); + EXPECT_GE(out_rerank->scores[i - 1], out_rerank->scores[i]); } } - ret = Alg_Destroy(handle); + out_sp.reset(); + outputs.clear(); + ret = op.Destroy(handle); EXPECT_EQ(ret, 0); std::error_code ec; std::filesystem::remove_all(temp_dir, ec); diff --git a/tests/integration/runtime/test_runtime_control_and_hot_swap.cpp b/tests/integration/runtime/test_runtime_control_and_hot_swap.cpp index 4bb40168..0d74d79b 100644 --- a/tests/integration/runtime/test_runtime_control_and_hot_swap.cpp +++ b/tests/integration/runtime/test_runtime_control_and_hot_swap.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -11,22 +12,21 @@ #include "core/common_contracts.h" #include "core/pipeline.h" -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" - -static std::string GetConfigPath(const std::string& rel_path) { - FILE* fp = fopen(rel_path.c_str(), "r"); - if (fp) { - fclose(fp); - return rel_path; - } - return "../" + rel_path; -} +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" +#include "platform_mock/error_codes.h" +#include "platform_mock/operator_data_types.h" + +namespace llm_edgeflow { class RuntimeControlAndHotSwapTest : public ::testing::Test { protected: - void SetUp() override { Alg_Init(); } - void TearDown() override { Alg_DeInit(); } + void SetUp() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } }; namespace { @@ -169,27 +169,37 @@ TEST_F(RuntimeControlAndHotSwapTest, // 1. 关键词库运行时动态热更新与立即生效测试 TEST_F(RuntimeControlAndHotSwapTest, KeywordMatcherDynamicHotSwap) { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); + ASSERT_EQ(op.Create(&handle, ¶m), 0); ASSERT_NE(handle, nullptr); // 1.1 初始状态测试:默认无 VIP_URGENT 命中 - const char* input_text_1 = "这是一个普通的测试,包含 VIP 专席客户服务。"; - CompanyKeywordInputStruct in_req_1{10001, input_text_1}; - std::vector inputs_1 = {&in_req_1}; - CompanyKeywordOutputStruct out_res_1; - std::vector outputs_1 = {&out_res_1}; - - int ret = Alg_Process(handle, inputs_1, outputs_1); + std::string input_text_1 = "这是一个普通的测试,包含 VIP 专席客户服务。"; + CompanyString cs1{static_cast(input_text_1.size()), + const_cast(input_text_1.data())}; + CompanyOperatorKeywordInput in_req_1{10001, &cs1}; + + operator_api::NamedIoBatch inputs_1(1); + inputs_1[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req_1); + operator_api::NamedIoBatch outputs_1(1); + outputs_1[0]["client_channel.keyword_out"] = nullptr; + + int ret = op.Process(handle, inputs_1, outputs_1); EXPECT_EQ(ret, 0); - EXPECT_EQ(out_res_1.is_hit, 0); + ASSERT_EQ(outputs_1.size(), 1u); + auto out1_sp = outputs_1[0]["client_channel.keyword_out"]; + ASSERT_NE(out1_sp, nullptr); + auto* out_res_1 = static_cast(out1_sp.get()); + EXPECT_EQ(out_res_1->is_hit, 0); // 1.2 运行时热下发新词库类别 "VIP_URGENT": ["VIP", "专席"] nlohmann::json control_param = {{"categories", @@ -200,52 +210,71 @@ TEST_F(RuntimeControlAndHotSwapTest, KeywordMatcherDynamicHotSwap) { {"payload", control_param}}; std::string param_str = envelope.dump(); - CompanyAlgParamControl ctrl; - ctrl.control_cmd = 1; - ctrl.json_param_str = param_str.c_str(); - ret = Alg_Control(handle, &ctrl); + operator_api::ControlUpdateRulesParam ctrl{param_str.c_str()}; + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, &ctrl); EXPECT_EQ(ret, 0); // 1.3 再次执行匹配,验证新词库已即时生效并命中 - ret = Alg_Process(handle, inputs_1, outputs_1); + outputs_1[0]["client_channel.keyword_out"] = nullptr; + ret = op.Process(handle, inputs_1, outputs_1); EXPECT_EQ(ret, 0); - EXPECT_EQ(out_res_1.is_hit, 1); - nlohmann::json match_json = - nlohmann::json::parse(out_res_1.match_result_json); + out1_sp = outputs_1[0]["client_channel.keyword_out"]; + ASSERT_NE(out1_sp, nullptr); + out_res_1 = static_cast(out1_sp.get()); + EXPECT_EQ(out_res_1->is_hit, 1); + ASSERT_NE(out_res_1->match_result_json, nullptr); + std::string match_str(out_res_1->match_result_json->data, + out_res_1->match_result_json->length); + nlohmann::json match_json = nlohmann::json::parse(match_str); ASSERT_TRUE(match_json.contains("matches")); ASSERT_FALSE(match_json["matches"].empty()); EXPECT_EQ(match_json["matches"][0]["category"], "VIP_URGENT"); // 1.4 验证第二条新词库 DISCOUNT_PROMO - const char* input_text_2 = "扫码立即返现50元优惠券!"; - CompanyKeywordInputStruct in_req_2{10002, input_text_2}; - std::vector inputs_2 = {&in_req_2}; - CompanyKeywordOutputStruct out_res_2; - std::vector outputs_2 = {&out_res_2}; - - ret = Alg_Process(handle, inputs_2, outputs_2); + std::string input_text_2 = "扫码立即返现50元优惠券!"; + CompanyString cs2{static_cast(input_text_2.size()), + const_cast(input_text_2.data())}; + CompanyOperatorKeywordInput in_req_2{10002, &cs2}; + + operator_api::NamedIoBatch inputs_2(1); + inputs_2[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req_2); + operator_api::NamedIoBatch outputs_2(1); + outputs_2[0]["client_channel.keyword_out"] = nullptr; + + ret = op.Process(handle, inputs_2, outputs_2); EXPECT_EQ(ret, 0); - EXPECT_EQ(out_res_2.is_hit, 1); - nlohmann::json match_json_2 = - nlohmann::json::parse(out_res_2.match_result_json); + auto out2_sp = outputs_2[0]["client_channel.keyword_out"]; + ASSERT_NE(out2_sp, nullptr); + auto* out_res_2 = static_cast(out2_sp.get()); + EXPECT_EQ(out_res_2->is_hit, 1); + ASSERT_NE(out_res_2->match_result_json, nullptr); + std::string match_str_2(out_res_2->match_result_json->data, + out_res_2->match_result_json->length); + nlohmann::json match_json_2 = nlohmann::json::parse(match_str_2); ASSERT_TRUE(match_json_2.contains("matches")); ASSERT_FALSE(match_json_2["matches"].empty()); EXPECT_EQ(match_json_2["matches"][0]["category"], "DISCOUNT_PROMO"); - Alg_Destroy(handle); + out1_sp.reset(); + out2_sp.reset(); + outputs_1.clear(); + outputs_2.clear(); + op.Destroy(handle); } -// 2. 同一 handle 的 Process/Control 由 C ABI 层串行化,停流 join 后再销毁 +// 2. 同一 handle 的 Process/Control 由 Operator 层串行化,停流 join 后再销毁 TEST_F(RuntimeControlAndHotSwapTest, ConcurrentProcessAndHotControl) { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); + ASSERT_EQ(op.Create(&handle, ¶m), 0); ASSERT_NE(handle, nullptr); std::atomic start_flag{false}; @@ -257,13 +286,17 @@ TEST_F(RuntimeControlAndHotSwapTest, ConcurrentProcessAndHotControl) { // 线程 1: 持续发起推理 std::thread process_thread([&]() { while (!start_flag.load()) std::this_thread::yield(); - const char* text = "测试动态控制下的并发推理稳定性,含有VIP关键词"; + std::string text = "测试动态控制下的并发推理稳定性,含有VIP关键词"; + CompanyString cs{static_cast(text.size()), + const_cast(text.data())}; for (int iteration = 0; iteration < kProcessIterations; ++iteration) { - CompanyKeywordInputStruct in_req{10003, text}; - std::vector inputs = {&in_req}; - CompanyKeywordOutputStruct out_res; - std::vector outputs = {&out_res}; - int ret = Alg_Process(handle, inputs, outputs); + CompanyOperatorKeywordInput in_req{10003, &cs}; + operator_api::NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req); + operator_api::NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = nullptr; + int ret = op.Process(handle, inputs, outputs); if (ret == 0) { process_count.fetch_add(1); } @@ -278,10 +311,9 @@ TEST_F(RuntimeControlAndHotSwapTest, ConcurrentProcessAndHotControl) { {{"DYNAMIC_CAT_" + std::to_string(iter % 5), {"VIP", "测试", "动态"}}}}}; std::string s = ctrl_json.dump(); - CompanyAlgParamControl ctrl; - ctrl.control_cmd = 1; - ctrl.json_param_str = s.c_str(); - int ret = Alg_Control(handle, &ctrl); + operator_api::ControlUpdateRulesParam ctrl{s.c_str()}; + int ret = + op.Control(handle, operator_api::ControlCommand::kUpdateRules, &ctrl); if (ret == 0) { control_count.fetch_add(1); } @@ -296,44 +328,48 @@ TEST_F(RuntimeControlAndHotSwapTest, ConcurrentProcessAndHotControl) { EXPECT_EQ(process_count.load(), kProcessIterations); EXPECT_EQ(control_count.load(), kControlIterations); - EXPECT_EQ(Alg_Destroy(handle), 0); + EXPECT_EQ(op.Destroy(handle), 0); } // 3. 非法控制指令与边界容错测试 TEST_F(RuntimeControlAndHotSwapTest, InvalidControlCommands) { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); + ASSERT_EQ(op.Create(&handle, ¶m), 0); ASSERT_NE(handle, nullptr); // 3.1 非法指令码 cmd = 99999 (未声明命令返回 // COMPANY_ALG_ERR_UNSUPPORTED_CONTROL = -7) - CompanyAlgParamControl ctrl1{99999, "{}"}; - int ret = Alg_Control(handle, &ctrl1); + operator_api::ControlJsonParam ctrl1{99999, "{}"}; + int ret = op.Control(handle, operator_api::ControlCommand::kJson, &ctrl1); EXPECT_EQ(ret, COMPANY_ALG_ERR_UNSUPPORTED_CONTROL); // 3.2 空指针参数 - ret = Alg_Control(handle, nullptr); - EXPECT_EQ(ret, -1); // adapter 层拦截空结构体指针 + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, nullptr); + EXPECT_EQ(ret, -2); // adapter 层拦截空结构体指针 - CompanyAlgParamControl ctrl_null_str{1, nullptr}; - ret = Alg_Control(handle, &ctrl_null_str); + operator_api::ControlUpdateRulesParam ctrl_null_str{nullptr}; + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, + &ctrl_null_str); EXPECT_EQ(ret, -2); // adapter 层拦截空 JSON 字符串指针 // 3.3 畸形 JSON 字符串 - CompanyAlgParamControl ctrl2{1, "{invalid_json_missing_brace"}; - ret = Alg_Control(handle, &ctrl2); - EXPECT_EQ(ret, -1); // node 层捕获 parse 异常并返回 -1 + operator_api::ControlUpdateRulesParam ctrl2{"{invalid_json_missing_brace"}; + ret = op.Control(handle, operator_api::ControlCommand::kUpdateRules, &ctrl2); + EXPECT_EQ(ret, -2); // adapter 层拦截畸形 JSON 并返回 -2 // 3.4 空句柄控制 - ret = Alg_Control(nullptr, &ctrl1); + ret = op.Control(nullptr, operator_api::ControlCommand::kJson, &ctrl1); EXPECT_EQ(ret, -1); - Alg_Destroy(handle); + op.Destroy(handle); } + +} // namespace llm_edgeflow diff --git a/tests/support/adapter_examples/flat_struct_adapter.h b/tests/support/adapter_examples/flat_struct_adapter.h index 5caf3cbc..9b6c52d8 100644 --- a/tests/support/adapter_examples/flat_struct_adapter.h +++ b/tests/support/adapter_examples/flat_struct_adapter.h @@ -6,7 +6,7 @@ #include "adapter/adapter_status.h" #include "adapter/adapter_validation_helper.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { namespace template_examples { diff --git a/tests/support/adapter_examples/nested_array_adapter.h b/tests/support/adapter_examples/nested_array_adapter.h index 7e90ce7c..605c821d 100644 --- a/tests/support/adapter_examples/nested_array_adapter.h +++ b/tests/support/adapter_examples/nested_array_adapter.h @@ -6,7 +6,7 @@ #include "adapter/adapter_status.h" #include "adapter/adapter_validation_helper.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { namespace template_examples { diff --git a/tests/support/adapter_examples/nested_pointer_tree_adapter.h b/tests/support/adapter_examples/nested_pointer_tree_adapter.h index e7311647..e857bc7e 100644 --- a/tests/support/adapter_examples/nested_pointer_tree_adapter.h +++ b/tests/support/adapter_examples/nested_pointer_tree_adapter.h @@ -6,7 +6,7 @@ #include "adapter/adapter_status.h" #include "adapter/adapter_validation_helper.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { namespace template_examples { diff --git a/tests/support/adapter_examples/tagged_union_adapter.h b/tests/support/adapter_examples/tagged_union_adapter.h index 3129bc30..bdb7b504 100644 --- a/tests/support/adapter_examples/tagged_union_adapter.h +++ b/tests/support/adapter_examples/tagged_union_adapter.h @@ -6,7 +6,7 @@ #include "adapter/adapter_status.h" #include "adapter/adapter_validation_helper.h" -#include "edgeflow/c_api.h" +#include "platform_mock/error_codes.h" namespace llm_edgeflow { namespace template_examples { diff --git a/tests/support/adapter_harness.h b/tests/support/adapter_harness.h index 606ae7f5..cf02fcae 100644 --- a/tests/support/adapter_harness.h +++ b/tests/support/adapter_harness.h @@ -19,7 +19,7 @@ namespace test { * @brief 测试专用 Adapter / Converter 契约夹具 * * 集中管理 AlgContext、句柄与输入输出视图生命周期, - * 提供来源扰动生成器和统一的 C ABI 解码/编码调用通道。 + * 提供来源扰动生成器和统一的 Operator 解码/编码调用通道。 */ class AdapterHarness { public: @@ -49,13 +49,18 @@ class AdapterHarness { AdapterStatus& Status() { return status_; } const AdapterStatus& Status() const { return status_; } - int DecodeCAbi(const std::vector& inputs) { + int DecodeOperator(const std::vector& inputs) { if (!in_conv_ || !in_conv_->decode_fn) return -1; ExternalInputBatchView view; - view.items = - inputs.empty() ? nullptr : const_cast(inputs.data()); view.count = inputs.size(); view.type_id = in_conv_->external_type; + std::string slot_name = in_conv_->external_slots.empty() + ? "" + : in_conv_->external_slots[0].slot_name; + if (!slot_name.empty()) { + view.slot_types[slot_name] = in_conv_->external_type; + view.leased_slots[slot_name] = inputs; + } InputDecodeOptions options; options.converter_id = in_conv_->converter_id; options.transport = in_conv_->transport; @@ -63,17 +68,22 @@ class AdapterHarness { } template - int EncodeCAbi(std::vector* outputs) { + int EncodeOperator(std::vector* outputs) { if (!out_conv_ || !out_conv_->encode_fn || !outputs) return -1; std::vector output_ptrs(outputs->size()); for (size_t i = 0; i < outputs->size(); ++i) { output_ptrs[i] = &(*outputs)[i]; } ExternalOutputBatchView view; - view.items = outputs->empty() ? nullptr : output_ptrs.data(); view.count = outputs->size(); - view.capacity = outputs->size(); view.type_id = out_conv_->external_type; + std::string slot_name = out_conv_->external_slots.empty() + ? "" + : out_conv_->external_slots[0].slot_name; + if (!slot_name.empty()) { + view.slot_types[slot_name] = out_conv_->external_type; + view.leased_slots[slot_name] = output_ptrs; + } OutputEncodeOptions options; options.converter_id = out_conv_->converter_id; options.transport = out_conv_->transport; diff --git a/tests/unit/adapter/test_adapter_purity.cpp b/tests/unit/adapter/test_adapter_purity.cpp index 36bfc396..043c30f3 100644 --- a/tests/unit/adapter/test_adapter_purity.cpp +++ b/tests/unit/adapter/test_adapter_purity.cpp @@ -19,17 +19,25 @@ #include "core/alg_context.h" #include "core/common_contracts.h" #include "core/pipeline_catalog.h" -#include "edgeflow/c_api.h" +#include "edgeflow/operator/interface.h" #include "edgeflow/operator/types.h" #include "engine/model_interface.h" #include "engine/model_registry.h" +#include "platform_mock/error_codes.h" +#include "platform_mock/operator_data_types.h" #include "tests/support/adapter_harness.h" namespace llm_edgeflow { class AdapterPurityTest : public ::testing::Test { protected: - void SetUp() override { SharedAlgorithmRuntime::GlobalInit(); } + void SetUp() override { + SharedAlgorithmRuntime::GlobalInit(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); + } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } }; struct CustomMultiFieldInput { @@ -40,6 +48,66 @@ struct CustomMultiFieldInput { DECLARE_EXTERNAL_TYPE_TRAITS(CustomMultiFieldInput, "CustomMultiFieldInput"); +struct DocOutputFixture { + char ans[512] = {0}; + CompanyString cs_ans{511, ans}; + char intent[128] = {0}; + CompanyString cs_intent{127, intent}; + CompanyOperatorDocOutput out{}; + DocOutputFixture() { + out.answer_text = &cs_ans; + out.intent_name = &cs_intent; + } +}; + +struct KeywordOutputFixture { + char match[2048] = {0}; + CompanyString cs_match{2047, match}; + CompanyOperatorKeywordOutput out{}; + KeywordOutputFixture() { out.match_result_json = &cs_match; } +}; + +struct EntityOutputFixture { + char entities[2048] = {0}; + CompanyString cs_entities{2047, entities}; + CompanyOperatorEntityOutput out{}; + EntityOutputFixture() { out.entities_json = &cs_entities; } +}; + +struct AuditOutputFixture { + char risk[64] = {0}; + CompanyString cs_risk{63, risk}; + char clause[512] = {0}; + CompanyString cs_clause{511, clause}; + char verdict[2048] = {0}; + CompanyString cs_verdict{2047, verdict}; + CompanyOperatorAuditOutput out{}; + AuditOutputFixture() { + out.risk_level = &cs_risk; + out.matched_policy_clause = &cs_clause; + out.audit_verdict_json = &cs_verdict; + } +}; + +struct OdOutputFixture { + char json[2048] = {0}; + CompanyString cs_json{2047, json}; + CompanyOdOutput out{}; + OdOutputFixture() { out.result_json = &cs_json; } +}; + +struct AudioOutputFixture { + char text[512] = {0}; + CompanyString cs_text{511, text}; + char slot[2048] = {0}; + CompanyString cs_slot{2047, slot}; + CompanyOperatorAudioOutput out{}; + AudioOutputFixture() { + out.transcribed_text = &cs_text; + out.intent_slot_json = &cs_slot; + } +}; + // ========================================================================= // 1. All 8 Businesses Converter Purity // ========================================================================= @@ -47,10 +115,10 @@ DECLARE_EXTERNAL_TYPE_TRAITS(CustomMultiFieldInput, "CustomMultiFieldInput"); // 1.1 DocQaConverter Purity (Biz 1) TEST_F(AdapterPurityTest, DocQaAdapterPurity) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "doc_query.plain.cabi.v1"); + "doc_query.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.cabi.v1"); + "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -63,12 +131,14 @@ TEST_F(AdapterPurityTest, DocQaAdapterPurity) { {"intent_matches", "intent_matches"}, {"doc_chunk_counts", "doc_chunk_counts"}})); - CompanyDocInputStruct in{}; - in.request_id = 1001; - in.doc_text = "Doc Content"; - in.query_text = "Query Question"; + std::string doc_str = "Doc Content"; + std::string query_str = "Query Question"; + CompanyString cs_doc{static_cast(doc_str.size()), doc_str.data()}; + CompanyString cs_query{static_cast(query_str.size()), + query_str.data()}; + CompanyOperatorDocInput in{1001, &cs_doc, &cs_query}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + ASSERT_EQ(harness.DecodeOperator({&in}), 0); const auto* req_ids = harness.Context().Read>("raw_request_ids"); @@ -95,23 +165,26 @@ TEST_F(AdapterPurityTest, DocQaAdapterPurity) { chunk_counts.emplace_back(0, 0, 1); harness.Publish("doc_chunk_counts", std::move(chunk_counts)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + DocOutputFixture doc_fix; + std::vector outputs = {doc_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1001u); EXPECT_EQ(outputs[0].chunk_count, 1); - EXPECT_STREQ(outputs[0].intent_name, "GENERAL_QA"); + ASSERT_NE(outputs[0].intent_name, nullptr); + EXPECT_STREQ(outputs[0].intent_name->data, "GENERAL_QA"); EXPECT_FLOAT_EQ(outputs[0].confidence, 0.95f); - EXPECT_STREQ(outputs[0].answer_text, "Model Generated Answer"); + ASSERT_NE(outputs[0].answer_text, nullptr); + EXPECT_STREQ(outputs[0].answer_text->data, "Model Generated Answer"); } // 1.2 KeywordMatchConverter Purity (Biz 2) TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "text.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.cabi.v1"); + "keyword.result.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -121,8 +194,10 @@ TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"rule_matches", "rule_matches"}})); - CompanyEntityInputStruct in{1002, "Some text"}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + std::string text_str = "Some text"; + CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; + CompanyOperatorEntityInput in{1002, &cs_text}; + ASSERT_EQ(harness.DecodeOperator({&in}), 0); RuleMatchBatch matches; matches.emplace_back( @@ -130,21 +205,23 @@ TEST_F(AdapterPurityTest, KeywordMatchAdapterPurity) { RuleMatchItem(1, "TEST_CAT", "测试", "{\"intent\":\"TEST_CAT\"}", 0.9f)); harness.Publish("rule_matches", std::move(matches)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + KeywordOutputFixture kw_fix; + std::vector outputs = {kw_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1002u); EXPECT_EQ(outputs[0].is_hit, 1); - EXPECT_STREQ(outputs[0].match_result_json, "{\"intent\":\"TEST_CAT\"}"); + ASSERT_NE(outputs[0].match_result_json, nullptr); + EXPECT_STREQ(outputs[0].match_result_json->data, "{\"intent\":\"TEST_CAT\"}"); } // 1.3 EntityExtractConverter Purity (Biz 3) TEST_F(AdapterPurityTest, EntityExtractAdapterPurity) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "text.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.cabi.v1"); + "document.structured.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -154,29 +231,33 @@ TEST_F(AdapterPurityTest, EntityExtractAdapterPurity) { OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"extracted_entities", "extracted_entities"}})); - CompanyEntityInputStruct in{1003, "Entity text"}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + std::string text_str = "Entity text"; + CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; + CompanyOperatorEntityInput in{1003, &cs_text}; + ASSERT_EQ(harness.DecodeOperator({&in}), 0); StructuredDocumentBatch entities; entities.emplace_back( 0, 0, JsonDocumentItem("[\"E1\"]", true, JsonParseStatus::kOk)); harness.Publish("extracted_entities", std::move(entities)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + EntityOutputFixture ent_fix; + std::vector outputs = {ent_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1003u); EXPECT_EQ(outputs[0].status_code, 0); - EXPECT_STREQ(outputs[0].entities_json, "[\"E1\"]"); + ASSERT_NE(outputs[0].entities_json, nullptr); + EXPECT_STREQ(outputs[0].entities_json->data, "[\"E1\"]"); } // 1.4 ComplianceAuditConverter Purity (Biz 4) TEST_F(AdapterPurityTest, ComplianceAuditAdapterPurity) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audit.plain.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audit.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.cabi.v1"); + "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -188,8 +269,12 @@ TEST_F(AdapterPurityTest, ComplianceAuditAdapterPurity) { {"structured_verdicts", "structured_verdicts"}, {"matched_policies", "matched_policies"}})); - CompanyAuditInputStruct in{1004, "audit sentence", "channel_vip"}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + std::string text_str = "audit sentence"; + std::string chan_str = "channel_vip"; + CompanyString cs_text{static_cast(text_str.size()), text_str.data()}; + CompanyString cs_chan{static_cast(chan_str.size()), chan_str.data()}; + CompanyOperatorAuditInput in{1004, &cs_text, &cs_chan}; + ASSERT_EQ(harness.DecodeOperator({&in}), 0); StructuredDocumentBatch verdicts; verdicts.emplace_back( @@ -203,62 +288,97 @@ TEST_F(AdapterPurityTest, ComplianceAuditAdapterPurity) { policies.emplace_back(0, 0, RankedCandidate("Clause 1", 1.0f, 1, 0)); harness.Publish("matched_policies", std::move(policies)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + AuditOutputFixture audit_fix; + std::vector outputs = {audit_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1004u); EXPECT_FLOAT_EQ(outputs[0].risk_score, 0.1f); - EXPECT_STREQ(outputs[0].risk_level, "SAFE"); - EXPECT_STREQ(outputs[0].matched_policy_clause, "Clause 1"); + ASSERT_NE(outputs[0].risk_level, nullptr); + EXPECT_STREQ(outputs[0].risk_level->data, "SAFE"); + ASSERT_NE(outputs[0].matched_policy_clause, nullptr); + EXPECT_STREQ(outputs[0].matched_policy_clause->data, "Clause 1"); } // 1.5 OcrDocQaConverter Purity (Biz 5) TEST_F(AdapterPurityTest, OcrDocQaAdapterPurity) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "image_query.plain.cabi.v1"); + "image_query.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "invoice_result.plain.cabi.v1"); + "invoice_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - test::AdapterHarness harness( - in_conv, out_conv, - InputPortBindings({{"raw_request_ids", "raw_request_ids"}, - {"image_paths", "image_paths"}, - {"user_queries", "user_queries"}}), - OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, - {"extracted_invoice_json", "extracted_invoice_json"}, - {"ocr_docs", "ocr_docs"}})); + std::string path_str = "/path/invoice.jpg"; + std::string query_str = "Total amount?"; + CompanyString cs_path{static_cast(path_str.size()), path_str.data()}; + CompanyString cs_query{static_cast(query_str.size()), + query_str.data()}; + CompanyFrame frame{1005, &cs_path, nullptr}; - CompanyOcrDocInputStruct in{1005, "/path/invoice.jpg", "Total amount?"}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + ExternalInputBatchView in_view; + in_view.count = 1; + in_view.leased_slots["frame"] = {&frame}; + in_view.leased_slots["string"] = {&cs_query}; + in_view.slot_types["frame"] = "CompanyFrame"; + in_view.slot_types["string"] = "CompanyString"; + + InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, + {"image_paths", "image_paths"}, + {"user_queries", "user_queries"}}); + InputDecodeOptions in_options; + in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; + + AlgContext ctx; + AdapterStatus status; + ASSERT_EQ(in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status), + 0); StructuredDocumentBatch invoices; invoices.emplace_back( 0, 0, JsonDocumentItem("{\"total\":99.9}", true, JsonParseStatus::kOk)); - harness.Publish("extracted_invoice_json", std::move(invoices)); + ctx.Publish("extracted_invoice_json", std::move(invoices)); OcrDocumentBatch ocr_docs; OcrDocumentItem ocr_item; ocr_item.boxes.push_back({0, 0, 10, 10, "Total", 0.99f}); ocr_docs.emplace_back(0, 0, std::move(ocr_item)); - harness.Publish("ocr_docs", std::move(ocr_docs)); + ctx.Publish("ocr_docs", std::move(ocr_docs)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + OdOutputFixture od_fix; + ExternalOutputBatchView out_view; + out_view.count = 1; + out_view.leased_slots["od_out"] = {&od_fix.out}; + out_view.slot_types["od_out"] = "CompanyOdOutput"; + out_view.slot_capacities["od_out"]["result_json"] = 2047; - EXPECT_EQ(outputs[0].request_id, 1005u); - EXPECT_EQ(outputs[0].detected_box_count, 1U); - EXPECT_STREQ(outputs[0].extracted_invoice_json, "{\"total\":99.9}"); + OutputPortBindings out_bindings( + {{"raw_request_ids", "raw_request_ids"}, + {"extracted_invoice_json", "extracted_invoice_json"}, + {"ocr_docs", "ocr_docs"}}); + OutputEncodeOptions out_options; + out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; + + size_t written = 0; + ASSERT_EQ(out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, + &written, &status), + 0); + + EXPECT_EQ(od_fix.out.request_id, 1005u); + EXPECT_EQ(od_fix.out.detected_box_count, 1); + ASSERT_NE(od_fix.out.result_json, nullptr); + EXPECT_STREQ(od_fix.out.result_json->data, "{\"total\":99.9}"); } // 1.6 AudioAsrIntentConverter Purity (Biz 6) TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audio.pcm.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audio.pcm.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audio_result.plain.cabi.v1"); + "audio_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -270,9 +390,9 @@ TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { {"intent_slots", "intent_slots"}})); std::vector pcm(1600, 0.05f); - CompanyAudioInputStruct in{1006, pcm.data(), static_cast(pcm.size()), - 16000}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + CompanyOperatorAudioInput in{1006, pcm.data(), static_cast(pcm.size()), + 16000}; + ASSERT_EQ(harness.DecodeOperator({&in}), 0); TextBatch transcripts; transcripts.emplace_back(0, 0, "turn left"); @@ -283,21 +403,24 @@ TEST_F(AdapterPurityTest, AudioAsrIntentAdapterPurity) { 0, 0, RuleMatchItem(1, "NAV", "", "{\"intent\":\"NAV\"}", 0.99f)); harness.Publish("intent_slots", std::move(intent_slots)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + AudioOutputFixture audio_fix; + std::vector outputs = {audio_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1006u); - EXPECT_STREQ(outputs[0].transcribed_text, "turn left"); - EXPECT_STREQ(outputs[0].intent_slot_json, "{\"intent\":\"NAV\"}"); + ASSERT_NE(outputs[0].transcribed_text, nullptr); + EXPECT_STREQ(outputs[0].transcribed_text->data, "turn left"); + ASSERT_NE(outputs[0].intent_slot_json, nullptr); + EXPECT_STREQ(outputs[0].intent_slot_json->data, "{\"intent\":\"NAV\"}"); } // 1.7 CrossRerankConverter Purity (Biz 7) TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "rerank.plain.cabi.v1"); + "rerank.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "rerank_result.plain.cabi.v1"); + "rerank_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -309,23 +432,31 @@ TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"ranked_results", "ranked_results"}})); - const char* passages[] = {"cand0", "cand1"}; - CompanyRerankBatchInputStruct in{}; + std::string q_str = "query"; + std::string cand0_str = "cand0"; + std::string cand1_str = "cand1"; + CompanyString cs_q{static_cast(q_str.size()), q_str.data()}; + CompanyString cs_cand0{static_cast(cand0_str.size()), + cand0_str.data()}; + CompanyString cs_cand1{static_cast(cand1_str.size()), + cand1_str.data()}; + + CompanyOperatorRerankInput in{}; in.request_id = 1007; - in.query_text = "query"; + in.query_text = &cs_q; + in.candidate_passages[0] = &cs_cand0; + in.candidate_passages[1] = &cs_cand1; in.candidate_count = 2; - in.candidate_passages[0] = passages[0]; - in.candidate_passages[1] = passages[1]; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + ASSERT_EQ(harness.DecodeOperator({&in}), 0); RankedTextBatch ranked; ranked.emplace_back(0, 0, RankedCandidate("cand1", 0.85f, 1, 1)); ranked.emplace_back(0, 1, RankedCandidate("cand0", 0.45f, 2, 0)); harness.Publish("ranked_results", std::move(ranked)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + std::vector outputs(1); + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1007u); EXPECT_EQ(outputs[0].count, 2); @@ -338,10 +469,10 @@ TEST_F(AdapterPurityTest, CrossRerankAdapterPurity) { // 1.8 TranslateConverter Purity (Biz 8) TEST_F(AdapterPurityTest, TranslateAdapterPurity) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -351,19 +482,24 @@ TEST_F(AdapterPurityTest, TranslateAdapterPurity) { OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}})); - CompanyEntityInputStruct in{1008, "{\"query\":\"Hello\"}"}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + std::string json_query = "{\"query\":\"Hello\"}"; + CompanyString cs_text{static_cast(json_query.size()), + json_query.data()}; + CompanyOperatorEntityInput in{1008, &cs_text}; + ASSERT_EQ(harness.DecodeOperator({&in}), 0); TextBatch answers; answers.emplace_back(0, 0, "Bonjour"); harness.Publish("llm_answers", std::move(answers)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + EntityOutputFixture ent_fix; + std::vector outputs = {ent_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1008u); EXPECT_EQ(outputs[0].status_code, 0); - auto parsed = nlohmann::json::parse(outputs[0].entities_json); + ASSERT_NE(outputs[0].entities_json, nullptr); + auto parsed = nlohmann::json::parse(outputs[0].entities_json->data); EXPECT_EQ(parsed["translated"], "Bonjour"); } @@ -373,7 +509,7 @@ TEST_F(AdapterPurityTest, TranslateAdapterPurity) { TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.cabi.v1"); + "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); OutputPortBindings out_bindings({{"raw_request_ids", "raw_request_ids"}, @@ -385,8 +521,9 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { { test::AdapterHarness harness(out_conv, out_bindings); harness.Publish("raw_request_ids", std::vector{1001}); - std::vector outputs(1); - EXPECT_NE(harness.EncodeCAbi(&outputs), 0); + DocOutputFixture fix; + std::vector outputs = {fix.out}; + EXPECT_NE(harness.EncodeOperator(&outputs), 0); } // Case 2: has llm_answers but missing intent_matches -> MUST fail-closed @@ -396,8 +533,9 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { TextBatch answers; answers.emplace_back(0, 0, "Some answer"); harness.Publish("llm_answers", std::move(answers)); - std::vector outputs(1); - EXPECT_EQ(harness.EncodeCAbi(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); + DocOutputFixture fix; + std::vector outputs = {fix.out}; + EXPECT_EQ(harness.EncodeOperator(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); } // Case 3: has intent_matches but missing explicit chunk counts -> MUST @@ -411,8 +549,9 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { RuleMatchBatch intents; intents.emplace_back(0, 0, RuleMatchItem(1, "QA", "", "{}", 0.9f)); harness.Publish("intent_matches", std::move(intents)); - std::vector outputs(1); - EXPECT_EQ(harness.EncodeCAbi(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); + DocOutputFixture fix; + std::vector outputs = {fix.out}; + EXPECT_EQ(harness.EncodeOperator(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); } // Case 4: all outputs exist but raw_request_ids is absent @@ -427,15 +566,16 @@ TEST_F(AdapterPurityTest, DocQaAdapter_FailClosedWhenMissingOutputs) { Int32Batch chunk_counts; chunk_counts.emplace_back(0, 0, 1); harness.Publish("doc_chunk_counts", std::move(chunk_counts)); - std::vector outputs(1); - EXPECT_NE(harness.EncodeCAbi(&outputs), 0); + DocOutputFixture fix; + std::vector outputs = {fix.out}; + EXPECT_NE(harness.EncodeOperator(&outputs), 0); } } TEST_F(AdapterPurityTest, ComplianceAuditAdapter_FailClosedWhenMissingStructuredFields) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.cabi.v1"); + "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); OutputPortBindings out_bindings( @@ -458,13 +598,14 @@ TEST_F(AdapterPurityTest, policies.emplace_back(0, 0, RankedCandidate("Clause", 1.0f, 1)); harness.Publish("matched_policies", std::move(policies)); - std::vector outputs(1); - EXPECT_EQ(harness.EncodeCAbi(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); + AuditOutputFixture fix; + std::vector outputs = {fix.out}; + EXPECT_EQ(harness.EncodeOperator(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); } TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.cabi.v1"); + "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); OutputPortBindings out_bindings( @@ -491,12 +632,14 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { {0, 1, {"req0 second", 0.5f, 2, 2}}, {1, 0, {"req1 first", 1.0f, 1, 1}}}); - std::vector outputs(2); - const int ret = harness.EncodeCAbi(&outputs); + AuditOutputFixture fix0, fix1; + std::vector outputs = {fix0.out, fix1.out}; + const int ret = harness.EncodeOperator(&outputs); if (parse_status == JsonParseStatus::kOk) { ASSERT_EQ(ret, 0) << harness.Status().ToString(); EXPECT_EQ(outputs[1].request_id, 200u); - EXPECT_STREQ(outputs[1].matched_policy_clause, "req1 first"); + ASSERT_NE(outputs[1].matched_policy_clause, nullptr); + EXPECT_STREQ(outputs[1].matched_policy_clause->data, "req1 first"); } else { EXPECT_NE(ret, 0); } @@ -505,7 +648,7 @@ TEST_F(AdapterPurityTest, AuditJoinsRankOneByRequestAndRejectsFallback) { TEST_F(AdapterPurityTest, OneToOneResultsRejectDuplicateAndOutOfRangeIds) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.cabi.v1"); + "keyword.result.operator.v1"); ASSERT_NE(out_conv, nullptr); OutputPortBindings out_bindings({{"raw_request_ids", "raw_request_ids"}, @@ -519,14 +662,15 @@ TEST_F(AdapterPurityTest, OneToOneResultsRejectDuplicateAndOutOfRangeIds) { for (auto id : ids) matches.emplace_back(id, 0, RuleMatchItem{}); harness.Publish("rule_matches", std::move(matches)); - std::vector outputs(2); - EXPECT_EQ(harness.EncodeCAbi(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); + KeywordOutputFixture fix0, fix1; + std::vector outputs = {fix0.out, fix1.out}; + EXPECT_EQ(harness.EncodeOperator(&outputs), COMPANY_ALG_ERR_INVALID_INPUT); } } TEST_F(AdapterPurityTest, ComplianceAuditAdapter_RejectsOversizedChannelName) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audit.plain.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audit.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, @@ -535,23 +679,31 @@ TEST_F(AdapterPurityTest, ComplianceAuditAdapter_RejectsOversizedChannelName) { const std::string valid_channel(256, 'c'); const std::string oversized_channel(257, 'c'); + std::string query_str = "test query"; + CompanyString cs_query{static_cast(query_str.size()), + query_str.data()}; // Valid length <= 256 { test::AdapterHarness harness(in_conv, in_bindings); - CompanyAuditInputStruct in{5001, "test query", valid_channel.c_str()}; - EXPECT_EQ(harness.DecodeCAbi({&in}), COMPANY_ALG_SUCCESS); + CompanyString cs_chan{static_cast(valid_channel.size()), + const_cast(valid_channel.data())}; + CompanyOperatorAuditInput in{5001, &cs_query, &cs_chan}; + EXPECT_EQ(harness.DecodeOperator({&in}), COMPANY_ALG_SUCCESS); } // Oversized length > 256 { test::AdapterHarness harness(in_conv, in_bindings); - CompanyAuditInputStruct in{5002, "test query", oversized_channel.c_str()}; - EXPECT_EQ(harness.DecodeCAbi({&in}), COMPANY_ALG_ERR_INVALID_INPUT); + CompanyString cs_chan{static_cast(oversized_channel.size()), + const_cast(oversized_channel.data())}; + CompanyOperatorAuditInput in{5002, &cs_query, &cs_chan}; + EXPECT_EQ(harness.DecodeOperator({&in}), COMPANY_ALG_ERR_INVALID_INPUT); } } -TEST_F(AdapterPurityTest, VariableDocResultPreservesLongAnswerAndCAbiLimit) { +TEST_F(AdapterPurityTest, + VariableDocResultPreservesLongAnswerAndOperatorCapacity) { const std::string long_answer(5000, 'a'); AlgContext ctx; @@ -561,64 +713,72 @@ TEST_F(AdapterPurityTest, VariableDocResultPreservesLongAnswerAndCAbiLimit) { RuleMatchBatch{{0, 0, RuleMatchItem(1, "QA", "", "{}", 0.9f)}}); ctx.Publish("doc_chunk_counts", Int32Batch{{0, 0, 1}}); - // 1. C ABI fixed buffer: sizeof(answer_text) is 1024, must return - // BUFFER_TOO_SMALL - const auto* cabi_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.cabi.v1"); - ASSERT_NE(cabi_conv, nullptr); - - CompanyDocOutputStruct cabi_out{}; - void* cabi_ptrs[] = {&cabi_out}; - ExternalOutputBatchView cabi_dest; - cabi_dest.items = cabi_ptrs; - cabi_dest.count = 1; - cabi_dest.capacity = 1; + const auto* op_conv = IoConverterRegistry::Instance().FindOutputConverter( + "doc_answer.plain.operator.v1"); + ASSERT_NE(op_conv, nullptr); OutputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}, {"intent_matches", "intent_matches"}, {"doc_chunk_counts", "doc_chunk_counts"}}); OutputEncodeOptions options; - options.converter_id = cabi_conv->converter_id; - size_t written = 0; - AdapterStatus status; - int ret = cabi_conv->encode_fn(&ctx, bindings, options, &cabi_dest, &written, + options.converter_id = op_conv->converter_id; + options.transport = "operator"; + + // 1. Operator buffer with small capacity (500) -> BUFFER_TOO_SMALL + { + CompanyOperatorDocOutput small_out{}; + std::vector ans_buf(500); + CompanyString cs_ans{0, ans_buf.data()}; + small_out.answer_text = &cs_ans; + std::vector int_buf(128); + CompanyString cs_int{0, int_buf.data()}; + small_out.intent_name = &cs_int; + + ExternalOutputBatchView small_dest; + small_dest.leased_slots["doc_out"].push_back(&small_out); + small_dest.slot_capacities["doc_out"]["answer_text"] = 499; + small_dest.slot_capacities["doc_out"]["intent_name"] = 127; + small_dest.count = 1; + + size_t written = 0; + AdapterStatus status; + int ret = op_conv->encode_fn(&ctx, bindings, options, &small_dest, &written, &status); - EXPECT_EQ(ret, COMPANY_ALG_ERR_BUFFER_TOO_SMALL); + EXPECT_EQ(ret, COMPANY_ALG_ERR_BUFFER_TOO_SMALL); + } // 2. Operator variable buffer: capacity = 6000, must succeed and preserve // full answer - const auto* op_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.operator.v1"); - ASSERT_NE(op_conv, nullptr); - - CompanyOperatorDocOutput op_out{}; - std::vector ans_buf(6000); - CompanyString cs_ans{0, ans_buf.data()}; - op_out.answer_text = &cs_ans; - - std::vector intent_buf(128); - CompanyString cs_intent{0, intent_buf.data()}; - op_out.intent_name = &cs_intent; - - ExternalOutputBatchView op_dest; - op_dest.leased_slots["doc_out"].push_back(&op_out); - op_dest.slot_capacities["doc_out"]["answer_text"] = 6000; - op_dest.slot_capacities["doc_out"]["intent_name"] = 128; - op_dest.count = 1; + { + CompanyOperatorDocOutput op_out{}; + std::vector ans_buf(6000); + CompanyString cs_ans{0, ans_buf.data()}; + op_out.answer_text = &cs_ans; + std::vector int_buf(128); + CompanyString cs_int{0, int_buf.data()}; + op_out.intent_name = &cs_int; + + ExternalOutputBatchView op_dest; + op_dest.leased_slots["doc_out"].push_back(&op_out); + op_dest.slot_capacities["doc_out"]["answer_text"] = 6000; + op_dest.slot_capacities["doc_out"]["intent_name"] = 128; + op_dest.count = 1; - options.converter_id = op_conv->converter_id; - ret = - op_conv->encode_fn(&ctx, bindings, options, &op_dest, &written, &status); - EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); - EXPECT_EQ(written, 1U); - EXPECT_EQ(op_out.request_id, 10U); - EXPECT_EQ(std::string(op_out.answer_text->data), long_answer); + size_t written = 0; + AdapterStatus status; + int ret = op_conv->encode_fn(&ctx, bindings, options, &op_dest, &written, + &status); + EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); + EXPECT_EQ(written, 1U); + EXPECT_EQ(op_out.request_id, 10U); + EXPECT_EQ(std::string(op_out.answer_text->data), long_answer); + } } TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); test::AdapterHarness harness( @@ -626,9 +786,10 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { {"input_sentences", "input_sentences"}})); std::string buffer = "{\"query\":\"original query\"}"; - CompanyEntityInputStruct in{5555, buffer.c_str()}; + CompanyString cs_buf{static_cast(buffer.size()), buffer.data()}; + CompanyOperatorEntityInput in{5555, &cs_buf}; - ASSERT_EQ(harness.DecodeCAbi({&in}), 0); + ASSERT_EQ(harness.DecodeOperator({&in}), 0); // Overwrite external buffer buffer[11] = 'X'; @@ -641,10 +802,10 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_CopyInPurity) { TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -654,10 +815,14 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}})); - CompanyEntityInputStruct in0{1234, "{\"query\":\"q0\"}"}; - CompanyEntityInputStruct in1{1234, "{\"query\":\"q1\"}"}; + std::string q0 = "{\"query\":\"q0\"}"; + std::string q1 = "{\"query\":\"q1\"}"; + CompanyString cs_q0{static_cast(q0.size()), q0.data()}; + CompanyString cs_q1{static_cast(q1.size()), q1.data()}; + CompanyOperatorEntityInput in0{1234, &cs_q0}; + CompanyOperatorEntityInput in1{1234, &cs_q1}; - ASSERT_EQ(harness.DecodeCAbi({&in0, &in1}), 0); + ASSERT_EQ(harness.DecodeOperator({&in0, &in1}), 0); const auto* req_ids = harness.Context().Read>("raw_request_ids"); @@ -672,25 +837,32 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_ExternalDuplicateIdsAllowed) { TextBatch answers{{0, 0, "ans0"}, {1, 0, "ans1"}}; harness.Publish("llm_answers", std::move(answers)); - std::vector outputs(2); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + EntityOutputFixture fix0, fix1; + std::vector outputs = {fix0.out, fix1.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1234u); EXPECT_EQ(outputs[1].request_id, 1234u); } TEST_F(AdapterPurityTest, InputBatchSkeleton_AllSamplesValidatedBeforePublish) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(in_conv, nullptr); test::AdapterHarness harness( in_conv, InputPortBindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}})); - CompanyEntityInputStruct in0{1, "{\"query\":\"valid\"}"}; - CompanyEntityInputStruct in1{2, "invalid json"}; + std::string valid_q = "{\"query\":\"valid\"}"; + std::string invalid_q = "invalid json"; + CompanyString cs_valid{static_cast(valid_q.size()), valid_q.data()}; + CompanyString cs_invalid{static_cast(invalid_q.size()), + invalid_q.data()}; + CompanyOperatorEntityInput in0{1, &cs_valid}; + CompanyOperatorEntityInput in1{2, &cs_invalid}; - EXPECT_EQ(harness.DecodeCAbi({&in0, &in1}), COMPANY_ALG_ERR_INVALID_INPUT); + EXPECT_EQ(harness.DecodeOperator({&in0, &in1}), + COMPANY_ALG_ERR_INVALID_INPUT); // AlgContext must be completely unpopulated EXPECT_EQ(harness.Context().Read>("raw_request_ids"), @@ -700,7 +872,7 @@ TEST_F(AdapterPurityTest, InputBatchSkeleton_AllSamplesValidatedBeforePublish) { TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.cabi.v1"); + "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); test::AdapterHarness harness( @@ -721,16 +893,21 @@ TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { harness.Publish("intent_matches", std::move(intents)); harness.Publish("doc_chunk_counts", std::move(chunks)); - std::vector outputs(2); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + DocOutputFixture fix0, fix1; + std::vector outputs = {fix0.out, fix1.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 1001u); - EXPECT_STREQ(outputs[0].answer_text, "Answer 0"); - EXPECT_STREQ(outputs[0].intent_name, "INTENT_0"); + ASSERT_NE(outputs[0].answer_text, nullptr); + EXPECT_STREQ(outputs[0].answer_text->data, "Answer 0"); + ASSERT_NE(outputs[0].intent_name, nullptr); + EXPECT_STREQ(outputs[0].intent_name->data, "INTENT_0"); EXPECT_EQ(outputs[0].chunk_count, 3); EXPECT_EQ(outputs[1].request_id, 2002u); - EXPECT_STREQ(outputs[1].answer_text, "Answer 1"); - EXPECT_STREQ(outputs[1].intent_name, "INTENT_1"); + ASSERT_NE(outputs[1].answer_text, nullptr); + EXPECT_STREQ(outputs[1].answer_text->data, "Answer 1"); + ASSERT_NE(outputs[1].intent_name, nullptr); + EXPECT_STREQ(outputs[1].intent_name->data, "INTENT_1"); EXPECT_EQ(outputs[1].chunk_count, 5); } @@ -742,64 +919,70 @@ TEST_F(AdapterPurityTest, DocQaAdapter_MultiWayResultsReorderedAndPerturbed) { // Test Binding TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { const auto* entity_binding = - IoBindingRegistry::Instance().FindBinding("entity_extract.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); ASSERT_NE(entity_binding, nullptr); const auto* keyword_binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); ASSERT_NE(keyword_binding, nullptr); - EXPECT_EQ(entity_binding->input_converter_id, "text.plain.cabi.v1"); - EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.cabi.v1"); + EXPECT_EQ(entity_binding->input_converter_id, "text.plain.operator.v1"); + EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.operator.v1"); - const auto* entity_conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); + const auto* entity_conv = IoConverterRegistry::Instance().FindInputConverter( + "text.plain.operator.v1"); ASSERT_NE(entity_conv, nullptr); - EXPECT_EQ(entity_conv->external_type, "CompanyEntityInputStruct"); + EXPECT_EQ(entity_conv->external_type, "CompanyOperatorEntityInput"); const auto* keyword_conv = IoConverterRegistry::Instance().FindInputConverter( - "keyword.plain.cabi.v1"); + "keyword.plain.operator.v1"); ASSERT_NE(keyword_conv, nullptr); - EXPECT_EQ(keyword_conv->external_type, "CompanyKeywordInputStruct"); + EXPECT_EQ(keyword_conv->external_type, "CompanyOperatorKeywordInput"); - // Decode input with entity binding: constructs CompanyEntityInputStruct + // Decode input with entity binding { test::AdapterHarness harness( entity_conv, InputPortBindings(entity_binding->input_ports)); - CompanyEntityInputStruct in{8001, "entity sentence"}; - EXPECT_EQ(harness.DecodeCAbi({&in}), 0); + std::string text_str = "entity sentence"; + CompanyString cs_text{static_cast(text_str.size()), + text_str.data()}; + CompanyOperatorEntityInput in{8001, &cs_text}; + EXPECT_EQ(harness.DecodeOperator({&in}), 0); const auto* sentences = harness.Context().Read("input_sentences"); ASSERT_NE(sentences, nullptr); EXPECT_EQ((*sentences)[0].data, "entity sentence"); } - // Decode input with keyword binding: constructs CompanyKeywordInputStruct + // Decode input with keyword binding { test::AdapterHarness harness( keyword_conv, InputPortBindings(keyword_binding->input_ports)); - CompanyKeywordInputStruct in{8002, "keyword sentence"}; - EXPECT_EQ(harness.DecodeCAbi({&in}), 0); + std::string text_str = "keyword sentence"; + CompanyString cs_text{static_cast(text_str.size()), + text_str.data()}; + CompanyOperatorKeywordInput in{8002, &cs_text}; + EXPECT_EQ(harness.DecodeOperator({&in}), 0); const auto* sentences = harness.Context().Read("input_sentences"); ASSERT_NE(sentences, nullptr); EXPECT_EQ((*sentences)[0].data, "keyword sentence"); } - // 跨业务复用证明:在测试专用绑定中复用 text.plain.cabi.v1 + // 跨业务复用证明:在测试专用绑定中复用 text.plain.operator.v1 { IoBindingDefinition test_reuse_binding; - test_reuse_binding.binding_id = "test_purity_reuse.cabi.v1"; + test_reuse_binding.binding_id = "test_purity_reuse.operator.v1"; test_reuse_binding.biz_name = "entity_extract_v1"; - test_reuse_binding.transport = "cabi"; - test_reuse_binding.input_converter_id = "text.plain.cabi.v1"; - test_reuse_binding.output_converter_id = "document.structured.cabi.v1"; + test_reuse_binding.transport = "operator"; + 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); - const auto* b_test = - IoBindingRegistry::Instance().FindBinding("test_purity_reuse.cabi.v1"); + const auto* b_test = IoBindingRegistry::Instance().FindBinding( + "test_purity_reuse.operator.v1"); ASSERT_NE(b_test, nullptr); EXPECT_EQ(b_test->input_converter_id, entity_binding->input_converter_id); } @@ -808,7 +991,7 @@ TEST_F(AdapterPurityTest, ReuseProof_1_InputConverterReusedAcrossBindings) { // Proof 2: Output Converter Reused Across Pipelines TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.cabi.v1"); + "document.structured.operator.v1"); ASSERT_NE(out_conv, nullptr); OutputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, @@ -824,10 +1007,12 @@ TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { JsonDocumentItem("[\"PERSON: Alice\"]", true, JsonParseStatus::kOk)); harness.Publish("extracted_entities", std::move(batch)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + EntityOutputFixture ent_fix; + std::vector outputs = {ent_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 9001U); - EXPECT_STREQ(outputs[0].entities_json, "[\"PERSON: Alice\"]"); + ASSERT_NE(outputs[0].entities_json, nullptr); + EXPECT_STREQ(outputs[0].entities_json->data, "[\"PERSON: Alice\"]"); } // Context B: Generic structured JSON pipeline output producing same schema @@ -840,26 +1025,27 @@ TEST_F(AdapterPurityTest, ReuseProof_2_OutputConverterReusedAcrossPipelines) { JsonDocumentItem("{\"summary\":\"ok\"}", true, JsonParseStatus::kOk)); harness.Publish("extracted_entities", std::move(batch)); - std::vector outputs(1); - ASSERT_EQ(harness.EncodeCAbi(&outputs), 0); + EntityOutputFixture ent_fix; + std::vector outputs = {ent_fix.out}; + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); EXPECT_EQ(outputs[0].request_id, 9002U); - EXPECT_STREQ(outputs[0].entities_json, "{\"summary\":\"ok\"}"); + ASSERT_NE(outputs[0].entities_json, nullptr); + EXPECT_STREQ(outputs[0].entities_json->data, "{\"summary\":\"ok\"}"); } } // Proof 3: Multiple External Input Formats Driving Same Pipeline TEST_F(AdapterPurityTest, ReuseProof_3_MultipleExternalInputFormatsForSamePipeline) { - // Register custom input converter that converts CustomMultiFieldInput to - // TextBatch InputConverterDefinition custom_in_def; - custom_in_def.converter_id = "test.multi_field.cabi.v1"; - custom_in_def.transport = "cabi"; + custom_in_def.converter_id = "test.multi_field.operator.v1"; + custom_in_def.transport = "operator"; custom_in_def.schema_id = "multi_field.request"; custom_in_def.schema_version = 1; custom_in_def.external_type = "CustomMultiFieldInput"; custom_in_def.external_slots = {ExternalSlotDefinition( - "inputs", "CustomMultiFieldInput", PortDirection::kInput, true)}; + "inputs", "CustomMultiFieldInput", PortDirection::kInput, true, + "CustomMultiFieldInput", "custom_input")}; custom_in_def.max_batch_size = 64; custom_in_def.logical_ports = { NodePortDefinition("raw_request_ids", "vector", true, "1:1"), @@ -871,7 +1057,7 @@ TEST_F(AdapterPurityTest, std::vector ids; TextBatch texts; for (size_t i = 0; i < src.count; ++i) { - const auto* item = src.GetCAbi(i); + const auto* item = src.GetSlot("inputs", i); if (!item) return -3; ids.push_back(item->req_id); std::string combined = @@ -886,47 +1072,51 @@ TEST_F(AdapterPurityTest, EXPECT_TRUE( IoConverterRegistry::Instance().RegisterInputConverter(custom_in_def)); - // Format A: CompanyEntityInputStruct via text.plain.cabi.v1 + // Format A: CompanyOperatorEntityInput via text.plain.operator.v1 AlgContext ctx_a; { const auto* in_a = IoConverterRegistry::Instance().FindInputConverter( - "text.plain.cabi.v1"); + "text.plain.operator.v1"); ASSERT_NE(in_a, nullptr); - CompanyEntityInputStruct req_a{777, "AI: Revolution in robotics"}; - const void* items[] = {&req_a}; + std::string text_str = "AI: Revolution in robotics"; + CompanyString cs_text{static_cast(text_str.size()), + text_str.data()}; + CompanyOperatorEntityInput req_a{777, &cs_text}; ExternalInputBatchView view; - view.items = items; + view.leased_slots["entity_in"] = {&req_a}; + view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; view.count = 1; - view.type_id = "CompanyEntityInputStruct"; + view.type_id = "CompanyOperatorEntityInput"; InputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions opts; opts.converter_id = in_a->converter_id; + opts.transport = "operator"; AdapterStatus st; ASSERT_EQ(in_a->decode_fn(view, opts, bindings, &ctx_a, &st), 0); } - // Format B: CustomMultiFieldInput via test.multi_field.cabi.v1 + // Format B: CustomMultiFieldInput via test.multi_field.operator.v1 AlgContext ctx_b; { const auto* in_b = IoConverterRegistry::Instance().FindInputConverter( - "test.multi_field.cabi.v1"); + "test.multi_field.operator.v1"); ASSERT_NE(in_b, nullptr); CustomMultiFieldInput req_b{777, "AI", "Revolution in robotics"}; - const void* items[] = {&req_b}; ExternalInputBatchView view; - view.items = items; + view.leased_slots["inputs"] = {&req_b}; + view.slot_types["inputs"] = "CustomMultiFieldInput"; view.count = 1; view.type_id = "CustomMultiFieldInput"; InputPortBindings bindings( {{"raw_request_ids", "raw_request_ids"}, {"texts", "input_sentences"}}); InputDecodeOptions opts; opts.converter_id = in_b->converter_id; + opts.transport = "operator"; AdapterStatus st; ASSERT_EQ(in_b->decode_fn(view, opts, bindings, &ctx_b, &st), 0); } - // Both produce identical internal TextBatch on "input_sentences" const auto* texts_a = ctx_a.Read("input_sentences"); const auto* texts_b = ctx_b.Read("input_sentences"); ASSERT_NE(texts_a, nullptr); @@ -937,90 +1127,81 @@ TEST_F(AdapterPurityTest, // Proof 4: Independently Switch Output Formats for Same Pipeline TEST_F(AdapterPurityTest, ReuseProof_4_IndependentlySwitchOutputFormat) { - // Prepare common AlgContext with both structured document and keyword results - AlgContext ctx; - ctx.Publish("raw_request_ids", std::vector{5001}); - - StructuredDocumentBatch docs; - docs.emplace_back( - 0, 0, JsonDocumentItem("[\"item_1\"]", true, JsonParseStatus::kOk)); - ctx.Publish("extracted_entities", std::move(docs)); - - RuleMatchBatch rules; - rules.emplace_back( - 0, 0, RuleMatchItem(1, "URGENT", "急", "{\"flag\":\"urgent\"}", 0.99f)); - ctx.Publish("rule_matches", std::move(rules)); - - // Binding Output A: document.structured.cabi.v1 -> CompanyEntityOutputStruct + // Binding Output A: document.structured.operator.v1 -> + // CompanyOperatorEntityOutput { const auto* out_a = IoConverterRegistry::Instance().FindOutputConverter( - "document.structured.cabi.v1"); + "document.structured.operator.v1"); ASSERT_NE(out_a, nullptr); - CompanyEntityOutputStruct out{}; - void* items[] = {&out}; - ExternalOutputBatchView dest; - dest.items = items; - dest.count = 1; - dest.capacity = 1; - dest.type_id = out_a->external_type; - OutputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, - {"extracted_entities", "extracted_entities"}}); - OutputEncodeOptions opts; - opts.converter_id = out_a->converter_id; - size_t written = 0; - AdapterStatus st; - ASSERT_EQ(out_a->encode_fn(&ctx, bindings, opts, &dest, &written, &st), 0); - EXPECT_EQ(out.request_id, 5001U); - EXPECT_STREQ(out.entities_json, "[\"item_1\"]"); + EntityOutputFixture fix; + std::vector outputs = {fix.out}; + test::AdapterHarness harness( + out_a, + OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, + {"extracted_entities", "extracted_entities"}})); + harness.Publish("raw_request_ids", std::vector{5001}); + harness.Publish( + "extracted_entities", + StructuredDocumentBatch{ + {0, 0, + JsonDocumentItem("[\"item_1\"]", true, JsonParseStatus::kOk)}}); + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); + EXPECT_EQ(outputs[0].request_id, 5001U); + ASSERT_NE(outputs[0].entities_json, nullptr); + EXPECT_STREQ(outputs[0].entities_json->data, "[\"item_1\"]"); } - // Binding Output B: keyword.result.cabi.v1 -> CompanyKeywordOutputStruct + // Binding Output B: keyword.result.operator.v1 -> + // CompanyOperatorKeywordOutput { const auto* out_b = IoConverterRegistry::Instance().FindOutputConverter( - "keyword.result.cabi.v1"); + "keyword.result.operator.v1"); ASSERT_NE(out_b, nullptr); - CompanyKeywordOutputStruct out{}; - void* items[] = {&out}; - ExternalOutputBatchView dest; - dest.items = items; - dest.count = 1; - dest.capacity = 1; - dest.type_id = out_b->external_type; - OutputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, - {"rule_matches", "rule_matches"}}); - OutputEncodeOptions opts; - opts.converter_id = out_b->converter_id; - size_t written = 0; - AdapterStatus st; - ASSERT_EQ(out_b->encode_fn(&ctx, bindings, opts, &dest, &written, &st), 0); - EXPECT_EQ(out.request_id, 5001U); - EXPECT_EQ(out.is_hit, 1); - EXPECT_STREQ(out.match_result_json, "{\"flag\":\"urgent\"}"); + KeywordOutputFixture fix; + std::vector outputs = {fix.out}; + test::AdapterHarness harness( + out_b, OutputPortBindings({{"raw_request_ids", "raw_request_ids"}, + {"rule_matches", "rule_matches"}})); + harness.Publish("raw_request_ids", std::vector{5001}); + harness.Publish( + "rule_matches", + RuleMatchBatch{{0, 0, + RuleMatchItem(1, "URGENT", "急", + "{\"flag\":\"urgent\"}", 0.99f)}}); + ASSERT_EQ(harness.EncodeOperator(&outputs), 0); + EXPECT_EQ(outputs[0].request_id, 5001U); + EXPECT_EQ(outputs[0].is_hit, 1); + ASSERT_NE(outputs[0].match_result_json, nullptr); + EXPECT_STREQ(outputs[0].match_result_json->data, "{\"flag\":\"urgent\"}"); } } // Proof 5: Same Carrier with Different Schemas TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { - const auto* plain_conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); + const auto* plain_conv = IoConverterRegistry::Instance().FindInputConverter( + "text.plain.operator.v1"); ASSERT_NE(plain_conv, nullptr); const auto* json_conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(json_conv, nullptr); // Payload 1: Pure plain text "Hello plain text" - CompanyEntityInputStruct plain_req{101, "Hello plain text"}; - const void* plain_items[] = {&plain_req}; + std::string plain_str = "Hello plain text"; + CompanyString cs_plain{static_cast(plain_str.size()), + plain_str.data()}; + CompanyOperatorEntityInput plain_req{101, &cs_plain}; ExternalInputBatchView plain_view; - plain_view.items = plain_items; + plain_view.leased_slots["entity_in"] = {&plain_req}; + plain_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; plain_view.count = 1; - plain_view.type_id = "CompanyEntityInputStruct"; + plain_view.type_id = "CompanyOperatorEntityInput"; InputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions opts; + opts.transport = "operator"; - // text.plain.cabi.v1 accepts it as plain text + // text.plain.operator.v1 accepts it as plain text { AlgContext ctx; AdapterStatus st; @@ -1031,7 +1212,7 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { EXPECT_EQ((*s)[0].data, "Hello plain text"); } - // translate.json.cabi.v1 rejects it because it is not JSON + // translate.json.operator.v1 rejects it because it is not JSON { AlgContext ctx; AdapterStatus st; @@ -1042,14 +1223,16 @@ TEST_F(AdapterPurityTest, ReuseProof_5_SameCarrierDifferentSchema) { } // Payload 2: JSON formatted string "{\"query\": \"Hello JSON\"}" - CompanyEntityInputStruct json_req{102, "{\"query\": \"Hello JSON\"}"}; - const void* json_items[] = {&json_req}; + std::string json_str = "{\"query\": \"Hello JSON\"}"; + CompanyString cs_json{static_cast(json_str.size()), json_str.data()}; + CompanyOperatorEntityInput json_req{102, &cs_json}; ExternalInputBatchView json_view; - json_view.items = json_items; + json_view.leased_slots["entity_in"] = {&json_req}; + json_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; json_view.count = 1; - json_view.type_id = "CompanyEntityInputStruct"; + json_view.type_id = "CompanyOperatorEntityInput"; - // translate.json.cabi.v1 succeeds and extracts "query" + // translate.json.operator.v1 succeeds and extracts "query" { AlgContext ctx; AdapterStatus st; @@ -1069,46 +1252,37 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { bad_binding_cfg.pipe_path = "pipeline_keyword_match_rules.json"; std::unique_ptr plan; std::string error; - int ret = IoBindingResolver::ResolveFromConfig(bad_binding_cfg, "cabi", + int ret = IoBindingResolver::ResolveFromConfig(bad_binding_cfg, "operator", "./models", &plan, &error); EXPECT_EQ(ret, -2); EXPECT_NE(error.find("Unknown or unregistered io_binding"), std::string::npos); - // 2. Transport mismatch: CABI requested, but operator binding specified + // 2. Transport mismatch: Non-operator transport requested DeploymentIoConfig mismatch_cfg; mismatch_cfg.io_binding = "keyword_match.operator.v1"; mismatch_cfg.pipe_path = "pipeline_keyword_match_rules.json"; - ret = IoBindingResolver::ResolveFromConfig(mismatch_cfg, "cabi", "./models", - &plan, &error); + ret = IoBindingResolver::ResolveFromConfig(mismatch_cfg, "legacy_cabi", + "./models", &plan, &error); EXPECT_EQ(ret, -2); - EXPECT_NE(error.find("Binding transport mismatch"), std::string::npos); // 3. DeploymentIoConfig schema validation rejects invalid version nlohmann::json invalid_version_json = { {"schema_version", 999}, {"data", - {{"pipe_path", "test.json"}, {"io_binding", "keyword_match.cabi.v1"}}}}; + {{"pipe_path", "test.json"}, + {"io_binding", "keyword_match.operator.v1"}}}}; DeploymentIoConfig parsed_cfg; - EXPECT_FALSE(DeploymentIoConfig::Parse(invalid_version_json, ".", "cabi", + EXPECT_FALSE(DeploymentIoConfig::Parse(invalid_version_json, ".", "operator", &parsed_cfg, &error)); EXPECT_NE(error.find("schema_version"), std::string::npos); - // 4. CABI config rejects outputs block - nlohmann::json cabi_with_outputs_json = { - {"schema_version", 1}, - {"data", - {{"pipe_path", "test.json"}, - {"io_binding", "keyword_match.cabi.v1"}, - {"outputs", {{"main", {{"type", "test"}}}}}}}}; - EXPECT_FALSE(DeploymentIoConfig::Parse(cabi_with_outputs_json, ".", "cabi", - &parsed_cfg, &error)); - EXPECT_NE(error.find("outputs"), std::string::npos); - - // 5. Operator config with unknown output slot rejected by parity check + // 4. Operator config with unknown output slot rejected by parity check DeploymentIoConfig unknown_out_cfg; unknown_out_cfg.io_binding = "keyword_match.operator.v1"; - unknown_out_cfg.pipe_path = "pipeline_keyword_match_rules.json"; + unknown_out_cfg.pipe_path = "configs/pipeline_keyword_match_rules.json"; + unknown_out_cfg.resolved_pipe_path = + "configs/pipeline_keyword_match_rules.json"; unknown_out_cfg.outputs = {{"unknown_slot", {{"type", "String"}}}}; ret = IoBindingResolver::ResolveFromConfig(unknown_out_cfg, "operator", "./models", &plan, &error); @@ -1116,33 +1290,38 @@ TEST_F(AdapterPurityTest, ReuseProof_6_NegativeCombinations) { EXPECT_NE(error.find("Unknown configured output slot: unknown_slot"), std::string::npos); - // 6. Unknown model_id in model_paths rejected + // 5. Unknown model_id in model_paths rejected DeploymentIoConfig unknown_mid_cfg; - unknown_mid_cfg.io_binding = "keyword_match.cabi.v1"; + unknown_mid_cfg.io_binding = "keyword_match.operator.v1"; unknown_mid_cfg.pipe_path = "configs/pipeline_keyword_match_rules.json"; unknown_mid_cfg.resolved_pipe_path = "configs/pipeline_keyword_match_rules.json"; + unknown_mid_cfg.outputs = {{"keyword_out", + {{"type", "keyword_out"}, + {"meta_num", 0}, + {"metadata_type_id", 0}, + {"capacities", {{"match_result_json", 2047}}}}}}; unknown_mid_cfg.model_paths = {{"non_existent_model", "dummy_path"}}; - ret = IoBindingResolver::ResolveFromConfig(unknown_mid_cfg, "cabi", + ret = IoBindingResolver::ResolveFromConfig(unknown_mid_cfg, "operator", "./models", &plan, &error); EXPECT_EQ(ret, -2); EXPECT_NE( error.find("Unknown model_id 'non_existent_model' in 'model_paths'"), - std::string::npos); + std::string::npos) + << "actual error was: " << error; } // Proof 7: Validation Before Initialization (probe model not loaded on invalid // binding) TEST_F(AdapterPurityTest, ReuseProof_7_ValidationBeforeInitialization) { - // Attempt to create algorithm instance with non-existent or invalid binding - // configuration - CompanyAlgParamCreate param{}; - param.config_file_path = "non_existent_path.json"; - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.cfg_file_name = "non_existent_path.conf"; + param.model_path = "./models"; param.device_id = 0; void* handle = nullptr; - int ret = Alg_Create(&handle, ¶m); + int ret = + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Create(&handle, ¶m); EXPECT_NE(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(handle, nullptr); } diff --git a/tests/unit/adapter/test_complex_converters.cpp b/tests/unit/adapter/test_complex_converters.cpp index f611e10b..f56d3fd6 100644 --- a/tests/unit/adapter/test_complex_converters.cpp +++ b/tests/unit/adapter/test_complex_converters.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -13,33 +14,41 @@ #include "core/alg_context.h" #include "core/common_contracts.h" #include "core/pipeline_catalog.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { class ComplexConvertersTest : public ::testing::Test {}; // ==================== DocQA ==================== -TEST_F(ComplexConvertersTest, DocQaCAbiInputAndOutput) { +TEST_F(ComplexConvertersTest, DocQaOperatorInputAndOutput) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "doc_query.plain.cabi.v1"); + "doc_query.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "doc_answer.plain.cabi.v1"); + "doc_answer.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - CompanyDocInputStruct doc_in{2001, "Sample document text", "What is sample?"}; - const void* in_items[] = {&doc_in}; + std::string doc_text = "Sample document text"; + std::string query_text = "What is sample?"; + CompanyString cs_doc{static_cast(doc_text.size()), + const_cast(doc_text.data())}; + CompanyString cs_query{static_cast(query_text.size()), + const_cast(query_text.data())}; + + CompanyOperatorDocInput doc_in{2001, &cs_doc, &cs_query}; ExternalInputBatchView in_view; - in_view.items = in_items; in_view.count = 1; + in_view.leased_slots["doc_in"] = {&doc_in}; + in_view.slot_types["doc_in"] = "CompanyOperatorDocInput"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"raw_docs", "raw_docs"}, {"raw_queries", "raw_queries"}}); InputDecodeOptions in_options; in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -60,15 +69,23 @@ TEST_F(ComplexConvertersTest, DocQaCAbiInputAndOutput) { ctx.Publish("intent_matches", std::move(intents)); Int32Batch chunk_counts; - chunk_counts.emplace_back(0, 0, 3); + chunk_counts.emplace_back(0, 0, 1); ctx.Publish("doc_chunk_counts", std::move(chunk_counts)); - CompanyDocOutputStruct doc_out{}; - void* out_items[] = {&doc_out}; + char ans_buf[256] = {0}; + CompanyString cs_ans{255, ans_buf}; + char int_buf[64] = {0}; + CompanyString cs_int{63, int_buf}; + CompanyOperatorDocOutput doc_out{}; + doc_out.answer_text = &cs_ans; + doc_out.intent_name = &cs_int; + ExternalOutputBatchView out_view; - out_view.items = out_items; out_view.count = 1; - out_view.capacity = 1; + out_view.leased_slots["doc_out"] = {&doc_out}; + out_view.slot_types["doc_out"] = "CompanyOperatorDocOutput"; + out_view.slot_capacities["doc_out"]["answer_text"] = 255; + out_view.slot_capacities["doc_out"]["intent_name"] = 63; OutputPortBindings out_bindings({{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}, @@ -76,6 +93,7 @@ TEST_F(ComplexConvertersTest, DocQaCAbiInputAndOutput) { {"doc_chunk_counts", "doc_chunk_counts"}}); OutputEncodeOptions out_options; out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; size_t written = 0; ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, @@ -83,34 +101,40 @@ TEST_F(ComplexConvertersTest, DocQaCAbiInputAndOutput) { EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(doc_out.request_id, 2001U); - EXPECT_STREQ(doc_out.intent_name, "general_faq"); - EXPECT_STREQ(doc_out.answer_text, "This is the answer."); + EXPECT_EQ(doc_out.chunk_count, 1); + ASSERT_NE(doc_out.intent_name, nullptr); + EXPECT_STREQ(doc_out.intent_name->data, "general_faq"); EXPECT_FLOAT_EQ(doc_out.confidence, 0.95f); - EXPECT_EQ(doc_out.chunk_count, 3); + ASSERT_NE(doc_out.answer_text, nullptr); + EXPECT_STREQ(doc_out.answer_text->data, "This is the answer."); } // ==================== CrossRerank ==================== -TEST_F(ComplexConvertersTest, CrossRerankCAbiInputAndOutput) { +TEST_F(ComplexConvertersTest, CrossRerankOperatorInputAndOutput) { const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( - "rerank.plain.cabi.v1"); + "rerank.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "rerank_result.plain.cabi.v1"); + "rerank_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - const char* passages[] = {"passage zero", "passage one", "passage two"}; - CompanyRerankBatchInputStruct rerank_in{}; + std::string query_text = "how to rerank?"; + std::string passage_text = "Reranking is a scoring step."; + CompanyString cs_q{static_cast(query_text.size()), + const_cast(query_text.data())}; + CompanyString cs_p{static_cast(passage_text.size()), + const_cast(passage_text.data())}; + + CompanyOperatorRerankInput rerank_in{}; rerank_in.request_id = 3001; - rerank_in.query_text = "what is passage"; - rerank_in.candidate_count = 3; - rerank_in.candidate_passages[0] = passages[0]; - rerank_in.candidate_passages[1] = passages[1]; - rerank_in.candidate_passages[2] = passages[2]; + rerank_in.query_text = &cs_q; + rerank_in.candidate_passages[0] = &cs_p; + rerank_in.candidate_count = 1; - const void* in_items[] = {&rerank_in}; ExternalInputBatchView in_view; - in_view.items = in_items; in_view.count = 1; + in_view.leased_slots["rerank_in"] = {&rerank_in}; + in_view.slot_types["rerank_in"] = "CompanyOperatorRerankInput"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"rerank_queries", "rerank_queries"}, @@ -118,30 +142,34 @@ TEST_F(ComplexConvertersTest, CrossRerankCAbiInputAndOutput) { {"rerank_pairs", "rerank_pairs"}}); InputDecodeOptions in_options; in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); - // Ranked results + // Setup ranked_results in context RankedTextBatch ranked; - ranked.emplace_back(0, 0, RankedCandidate("passage one", 0.9f, 1, 1)); - ranked.emplace_back(0, 1, RankedCandidate("passage zero", 0.5f, 2, 0)); - ranked.emplace_back(0, 2, RankedCandidate("passage two", 0.1f, 3, 2)); + RankedCandidate cand; + cand.rank = 1; + cand.score = 0.98f; + cand.original_sub_id = 0; + cand.text = "Reranking is a scoring step."; + ranked.emplace_back(0, 0, cand); ctx.Publish("ranked_results", std::move(ranked)); - CompanyRerankBatchOutputStruct rerank_out{}; - void* out_items[] = {&rerank_out}; + CompanyOperatorRerankOutput rerank_out{}; ExternalOutputBatchView out_view; - out_view.items = out_items; out_view.count = 1; - out_view.capacity = 1; + out_view.leased_slots["rerank_out"] = {&rerank_out}; + out_view.slot_types["rerank_out"] = "CompanyOperatorRerankOutput"; OutputPortBindings out_bindings({{"raw_request_ids", "raw_request_ids"}, {"ranked_results", "ranked_results"}}); OutputEncodeOptions out_options; out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; size_t written = 0; ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, @@ -149,60 +177,76 @@ TEST_F(ComplexConvertersTest, CrossRerankCAbiInputAndOutput) { EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(rerank_out.request_id, 3001U); - EXPECT_EQ(rerank_out.count, 3); - EXPECT_FLOAT_EQ(rerank_out.scores[0], 0.9f); - EXPECT_EQ(rerank_out.sorted_indices[0], 1); - EXPECT_FLOAT_EQ(rerank_out.scores[1], 0.5f); - EXPECT_EQ(rerank_out.sorted_indices[1], 0); + EXPECT_EQ(rerank_out.count, 1); + EXPECT_FLOAT_EQ(rerank_out.scores[0], 0.98f); + EXPECT_EQ(rerank_out.sorted_indices[0], 0); } // ==================== ComplianceAudit ==================== -TEST_F(ComplexConvertersTest, ComplianceAuditCAbiInputAndOutput) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audit.plain.cabi.v1"); +TEST_F(ComplexConvertersTest, DialogueAuditOperatorInputAndOutput) { + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audit.plain.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audit_result.plain.cabi.v1"); + "audit_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - CompanyAuditInputStruct audit_in{4001, "some dialogue text", "channel_vip"}; - const void* in_items[] = {&audit_in}; + std::string dialogue = "User text violating rules"; + std::string channel = "customer_service"; + CompanyString cs_dia{static_cast(dialogue.size()), + const_cast(dialogue.data())}; + CompanyString cs_chan{static_cast(channel.size()), + const_cast(channel.data())}; + + CompanyOperatorAuditInput audit_in{4001, &cs_dia, &cs_chan}; ExternalInputBatchView in_view; - in_view.items = in_items; in_view.count = 1; + in_view.leased_slots["audit_in"] = {&audit_in}; + in_view.slot_types["audit_in"] = "CompanyOperatorAuditInput"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"user_texts", "user_texts"}, {"channel_names", "channel_names"}}); InputDecodeOptions in_options; in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; int ret = in_conv->decode_fn(in_view, in_options, in_bindings, &ctx, &status); EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); - // Verdict document + // Setup audit results in context StructuredDocumentBatch verdicts; - JsonDocumentItem doc_item; - doc_item.is_valid = true; - doc_item.parse_status = JsonParseStatus::kOk; - doc_item.json_payload = "{\"risk_level\":\"SAFE\",\"risk_score\":0.05}"; - doc_item.structured_data = {{"risk_level", "SAFE"}, {"risk_score", 0.05f}}; - verdicts.emplace_back(0, 0, doc_item); + verdicts.emplace_back( + 0, 0, + JsonDocumentItem("{\"risk\":\"high\"}", true, JsonParseStatus::kOk, "", + {{"risk_level", "HIGH_RISK"}, {"risk_score", 0.88f}})); ctx.Publish("structured_verdicts", std::move(verdicts)); RankedTextBatch policies; - policies.emplace_back(0, 0, - RankedCandidate("Article 42.1 Policy", 1.0f, 1, 0)); + policies.emplace_back(0, 0, RankedCandidate("Rule 12.3", 0.88f, 1, 0)); ctx.Publish("matched_policies", std::move(policies)); - CompanyAuditOutputStruct audit_out{}; - void* out_items[] = {&audit_out}; + char risk_buf[32] = {0}; + CompanyString cs_risk{31, risk_buf}; + char clause_buf[256] = {0}; + CompanyString cs_clause{255, clause_buf}; + char verdict_buf[1024] = {0}; + CompanyString cs_verdict{1023, verdict_buf}; + + CompanyOperatorAuditOutput audit_out{}; + audit_out.risk_level = &cs_risk; + audit_out.matched_policy_clause = &cs_clause; + audit_out.audit_verdict_json = &cs_verdict; + ExternalOutputBatchView out_view; - out_view.items = out_items; out_view.count = 1; - out_view.capacity = 1; + out_view.leased_slots["audit_out"] = {&audit_out}; + out_view.slot_types["audit_out"] = "CompanyOperatorAuditOutput"; + out_view.slot_capacities["audit_out"]["risk_level"] = 31; + out_view.slot_capacities["audit_out"]["matched_policy_clause"] = 255; + out_view.slot_capacities["audit_out"]["audit_verdict_json"] = 1023; OutputPortBindings out_bindings( {{"raw_request_ids", "raw_request_ids"}, @@ -210,6 +254,7 @@ TEST_F(ComplexConvertersTest, ComplianceAuditCAbiInputAndOutput) { {"matched_policies", "matched_policies"}}); OutputEncodeOptions out_options; out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; size_t written = 0; ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, @@ -217,32 +262,37 @@ TEST_F(ComplexConvertersTest, ComplianceAuditCAbiInputAndOutput) { EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(audit_out.request_id, 4001U); - EXPECT_FLOAT_EQ(audit_out.risk_score, 0.05f); - EXPECT_STREQ(audit_out.risk_level, "SAFE"); - EXPECT_STREQ(audit_out.matched_policy_clause, "Article 42.1 Policy"); + ASSERT_NE(audit_out.risk_level, nullptr); + EXPECT_STREQ(audit_out.risk_level->data, "HIGH_RISK"); + EXPECT_FLOAT_EQ(audit_out.risk_score, 0.88f); + ASSERT_NE(audit_out.matched_policy_clause, nullptr); + EXPECT_STREQ(audit_out.matched_policy_clause->data, "Rule 12.3"); + ASSERT_NE(audit_out.audit_verdict_json, nullptr); + EXPECT_STREQ(audit_out.audit_verdict_json->data, "{\"risk\":\"high\"}"); } -// ==================== AudioAsrIntent ==================== -TEST_F(ComplexConvertersTest, AudioAsrIntentCAbiInputAndOutput) { - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audio.pcm.cabi.v1"); +// ==================== AudioAsr ==================== +TEST_F(ComplexConvertersTest, AudioAsrOperatorInputAndOutput) { + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audio.pcm.operator.v1"); ASSERT_NE(in_conv, nullptr); const auto* out_conv = IoConverterRegistry::Instance().FindOutputConverter( - "audio_result.plain.cabi.v1"); + "audio_result.plain.operator.v1"); ASSERT_NE(out_conv, nullptr); - std::vector pcm(1600, 0.1f); - CompanyAudioInputStruct audio_in{5001, pcm.data(), - static_cast(pcm.size()), 16000}; - const void* in_items[] = {&audio_in}; + std::vector pcm = {0.1f, 0.2f, -0.1f}; + CompanyOperatorAudioInput audio_in{5001, pcm.data(), + static_cast(pcm.size()), 16000}; ExternalInputBatchView in_view; - in_view.items = in_items; in_view.count = 1; + in_view.leased_slots["audio_in"] = {&audio_in}; + in_view.slot_types["audio_in"] = "CompanyOperatorAudioInput"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"audio_inputs", "audio_inputs"}}); InputDecodeOptions in_options; in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -261,18 +311,28 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentCAbiInputAndOutput) { slots.emplace_back(0, 0, m); ctx.Publish("intent_slots", std::move(slots)); - CompanyAudioOutputStruct audio_out{}; - void* out_items[] = {&audio_out}; + char trans_buf[512] = {0}; + CompanyString cs_trans{511, trans_buf}; + char slot_buf[1024] = {0}; + CompanyString cs_slot{1023, slot_buf}; + + CompanyOperatorAudioOutput audio_out{}; + audio_out.transcribed_text = &cs_trans; + audio_out.intent_slot_json = &cs_slot; + ExternalOutputBatchView out_view; - out_view.items = out_items; out_view.count = 1; - out_view.capacity = 1; + out_view.leased_slots["audio_out"] = {&audio_out}; + out_view.slot_types["audio_out"] = "CompanyOperatorAudioOutput"; + out_view.slot_capacities["audio_out"]["transcribed_text"] = 511; + out_view.slot_capacities["audio_out"]["intent_slot_json"] = 1023; OutputPortBindings out_bindings({{"raw_request_ids", "raw_request_ids"}, {"transcripts", "transcripts"}, {"intent_slots", "intent_slots"}}); OutputEncodeOptions out_options; out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; size_t written = 0; ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, @@ -280,8 +340,10 @@ TEST_F(ComplexConvertersTest, AudioAsrIntentCAbiInputAndOutput) { EXPECT_EQ(ret, COMPANY_ALG_SUCCESS); EXPECT_EQ(written, 1U); EXPECT_EQ(audio_out.request_id, 5001U); - EXPECT_STREQ(audio_out.transcribed_text, "open the front door"); - EXPECT_STREQ(audio_out.intent_slot_json, + ASSERT_NE(audio_out.transcribed_text, nullptr); + EXPECT_STREQ(audio_out.transcribed_text->data, "open the front door"); + ASSERT_NE(audio_out.intent_slot_json, nullptr); + EXPECT_STREQ(audio_out.intent_slot_json->data, "{\"intent\":\"open_door\",\"slot\":{\"target\":\"front\"}}"); } @@ -307,17 +369,18 @@ TEST_F(ComplexConvertersTest, OcrDocQaOperatorInputAndOutput) { const_cast(q_str.data())}; ExternalInputBatchView in_view; - auto frame_holder = std::shared_ptr(&frame, [](void*) {}); - auto query_holder = std::shared_ptr(&query, [](void*) {}); - in_view.slots["frame"].push_back(frame_holder); - in_view.slots["string"].push_back(query_holder); in_view.count = 1; + in_view.leased_slots["frame"] = {&frame}; + in_view.leased_slots["string"] = {&query}; + in_view.slot_types["frame"] = "CompanyFrame"; + in_view.slot_types["string"] = "CompanyString"; InputPortBindings in_bindings({{"raw_request_ids", "raw_request_ids"}, {"image_paths", "image_paths"}, {"user_queries", "user_queries"}}); InputDecodeOptions in_options; in_options.converter_id = in_conv->converter_id; + in_options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -342,12 +405,13 @@ TEST_F(ComplexConvertersTest, OcrDocQaOperatorInputAndOutput) { // Destination operator od_out CompanyOdOutput od_out{}; std::vector buf(256); - CompanyString res_str{0, buf.data()}; + CompanyString res_str{255, buf.data()}; od_out.result_json = &res_str; ExternalOutputBatchView out_view; - out_view.leased_slots["od_out"].push_back(&od_out); - out_view.slot_capacities["od_out"]["result_json"] = 256; + out_view.leased_slots["od_out"] = {&od_out}; + out_view.slot_types["od_out"] = "CompanyOdOutput"; + out_view.slot_capacities["od_out"]["result_json"] = 255; out_view.count = 1; OutputPortBindings out_bindings( @@ -356,6 +420,7 @@ TEST_F(ComplexConvertersTest, OcrDocQaOperatorInputAndOutput) { {"ocr_docs", "ocr_docs"}}); OutputEncodeOptions out_options; out_options.converter_id = out_conv->converter_id; + out_options.transport = "operator"; size_t written = 0; ret = out_conv->encode_fn(&ctx, out_bindings, out_options, &out_view, @@ -364,6 +429,7 @@ TEST_F(ComplexConvertersTest, OcrDocQaOperatorInputAndOutput) { EXPECT_EQ(written, 1U); EXPECT_EQ(od_out.request_id, 6001U); EXPECT_EQ(od_out.detected_box_count, 1); + ASSERT_NE(od_out.result_json, nullptr); EXPECT_STREQ(od_out.result_json->data, "{\"total\":123.45}"); } @@ -383,8 +449,13 @@ TEST_F(ComplexConvertersTest, AllEightBusinessesRegistered) { for (const auto& biz : expected_biz) { const auto* desc = IoBindingRegistry::Instance().FindExposure(biz); ASSERT_NE(desc, nullptr) << "Missing biz exposure: " << biz; - EXPECT_GE(desc->required_transports.size(), 2U) + EXPECT_GE(desc->required_transports.size(), 1U) << "Biz missing transports: " << biz; + bool has_operator = + std::find(desc->required_transports.begin(), + desc->required_transports.end(), + "operator") != desc->required_transports.end(); + EXPECT_TRUE(has_operator) << "Biz missing operator transport: " << biz; auto biz_def = PipelineCatalog::FindBiz(biz); ASSERT_TRUE(biz_def.has_value()) << "Missing biz in catalog: " << biz; diff --git a/tests/unit/adapter/test_io_binding_registry.cpp b/tests/unit/adapter/test_io_binding_registry.cpp index 2b75ed9f..865d7010 100644 --- a/tests/unit/adapter/test_io_binding_registry.cpp +++ b/tests/unit/adapter/test_io_binding_registry.cpp @@ -39,13 +39,14 @@ class IoBindingRegistryTest : public ::testing::Test { // 注册基础转换器供测试 InputConverterDefinition in_def; - in_def.converter_id = "test.in.cabi"; - in_def.transport = "cabi"; + in_def.converter_id = "test.in.operator"; + in_def.transport = "operator"; in_def.schema_id = "in_schema"; in_def.schema_version = 1; - in_def.external_type = "int"; - in_def.external_slots = { - ExternalSlotDefinition("inputs", "int", PortDirection::kInput, true)}; + in_def.external_type = "CompanyOperatorEntityInput"; + in_def.external_slots = {ExternalSlotDefinition( + "entity_in", "CompanyOperatorEntityInput", PortDirection::kInput, true, + "CompanyOperatorEntityInput", "entity_in")}; in_def.max_batch_size = 64; in_def.logical_ports = { NodePortDefinition("texts", "TextBatch", true, "1:1")}; @@ -53,13 +54,14 @@ class IoBindingRegistryTest : public ::testing::Test { IoConverterRegistry::Instance().RegisterInputConverter(in_def); OutputConverterDefinition out_def; - out_def.converter_id = "test.out.cabi"; - out_def.transport = "cabi"; + out_def.converter_id = "test.out.operator"; + out_def.transport = "operator"; out_def.schema_id = "out_schema"; out_def.schema_version = 1; - out_def.external_type = "int"; - out_def.external_slots = { - ExternalSlotDefinition("answers", "int", PortDirection::kOutput, true)}; + out_def.external_type = "CompanyOperatorEntityOutput"; + out_def.external_slots = {ExternalSlotDefinition( + "entity_out", "CompanyOperatorEntityOutput", PortDirection::kOutput, + true, "CompanyOperatorEntityOutput", "entity_out")}; out_def.max_batch_size = 64; out_def.logical_ports = { NodePortDefinition("answers", "TextBatch", true, "1:1")}; @@ -84,11 +86,11 @@ TEST_F(IoBindingRegistryTest, RegisterAndAuditValidBinding) { auto& reg = IoBindingRegistry::Instance(); IoBindingDefinition binding; - binding.binding_id = "test_biz.cabi.v1"; + binding.binding_id = "test_biz.operator.v1"; binding.biz_name = "test_biz_v1"; - binding.transport = "cabi"; - binding.input_converter_id = "test.in.cabi"; - binding.output_converter_id = "test.out.cabi"; + binding.transport = "operator"; + 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"}}; @@ -97,7 +99,7 @@ TEST_F(IoBindingRegistryTest, RegisterAndAuditValidBinding) { BizExposureDefinition exposure; exposure.biz_name = "test_biz_v1"; exposure.max_batch_size = 32; - exposure.required_transports = {"cabi"}; + exposure.required_transports = {"operator"}; EXPECT_TRUE(reg.RegisterExposure(exposure)); std::vector audit_errors; @@ -112,9 +114,9 @@ TEST_F(IoBindingRegistryTest, AuditRejectsUnregisteredConvertersAndBiz) { IoBindingDefinition bad_biz; bad_biz.binding_id = "bad_biz.binding"; bad_biz.biz_name = "non_existent_biz"; - bad_biz.transport = "cabi"; - bad_biz.input_converter_id = "test.in.cabi"; - bad_biz.output_converter_id = "test.out.cabi"; + bad_biz.transport = "operator"; + bad_biz.input_converter_id = "test.in.operator"; + bad_biz.output_converter_id = "test.out.operator"; reg.RegisterBinding(bad_biz); std::vector errors; @@ -133,9 +135,9 @@ TEST_F(IoBindingRegistryTest, AuditRejectsUnregisteredConvertersAndBiz) { IoBindingDefinition bad_conv; bad_conv.binding_id = "bad_conv.binding"; bad_conv.biz_name = "test_biz_v1"; - bad_conv.transport = "cabi"; + bad_conv.transport = "operator"; bad_conv.input_converter_id = "non_existent_input"; - bad_conv.output_converter_id = "test.out.cabi"; + bad_conv.output_converter_id = "test.out.operator"; reg.RegisterBinding(bad_conv); errors.clear(); @@ -175,11 +177,11 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { // 1. 注册合法绑定与曝光 IoBindingDefinition valid_binding; - valid_binding.binding_id = "test_biz.cabi.v1"; + valid_binding.binding_id = "test_biz.operator.v1"; valid_binding.biz_name = "test_biz_v1"; - valid_binding.transport = "cabi"; - valid_binding.input_converter_id = "test.in.cabi"; - valid_binding.output_converter_id = "test.out.cabi"; + valid_binding.transport = "operator"; + 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)); @@ -187,7 +189,7 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { BizExposureDefinition exposure; exposure.biz_name = "test_biz_v1"; exposure.max_batch_size = 32; - exposure.required_transports = {"cabi"}; + exposure.required_transports = {"operator"}; EXPECT_TRUE(reg.RegisterExposure(exposure)); // 单独 audit 合法绑定应当通过 @@ -197,11 +199,11 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { // 2. 注册未被选择使用的非法绑定 (缺失必需输入映射) IoBindingDefinition illegal_binding; - illegal_binding.binding_id = "unselected_bad.cabi.v1"; + illegal_binding.binding_id = "unselected_bad.operator.v1"; illegal_binding.biz_name = "test_biz_v1"; - illegal_binding.transport = "cabi"; - illegal_binding.input_converter_id = "test.in.cabi"; - illegal_binding.output_converter_id = "test.out.cabi"; + illegal_binding.transport = "operator"; + illegal_binding.input_converter_id = "test.in.operator"; + illegal_binding.output_converter_id = "test.out.operator"; illegal_binding.input_ports = {}; // 缺失必需 logical port texts illegal_binding.output_ports = {{"answers", "llm_answers"}}; EXPECT_TRUE(reg.RegisterBinding(illegal_binding)); @@ -226,11 +228,18 @@ TEST_F(IoBindingRegistryTest, UnselectedIllegalBindingFailsAudit) { } TEST_F(IoBindingRegistryTest, DeploymentIoConfigValidation) { - // 1. 合法 schema 1 C ABI 配置 + // 1. 合法 schema 1 Operator 配置 nlohmann::json valid_cfg = { {"schema_version", 1}, {"data", - {{"pipe_path", "test.json"}, {"io_binding", "test_biz.cabi.v1"}}}}; + {{"pipe_path", "test.json"}, + {"io_binding", "test_biz.operator.v1"}, + {"outputs", + {{"answers", + {{"type", "answers"}, + {"meta_num", 0}, + {"metadata_type_id", 0}, + {"capacities", nlohmann::json::object()}}}}}}}}; // 写入临时测试 pipeline 文件 std::string tmp_dir = "/tmp/edgeflow_test_config_" + std::to_string(getpid()); @@ -244,33 +253,31 @@ TEST_F(IoBindingRegistryTest, DeploymentIoConfigValidation) { DeploymentIoConfig parsed; std::string err; EXPECT_TRUE( - DeploymentIoConfig::Parse(valid_cfg, tmp_dir, "cabi", &parsed, &err)); + DeploymentIoConfig::Parse(valid_cfg, tmp_dir, "operator", &parsed, &err)); EXPECT_EQ(parsed.pipe_path, "test.json"); - EXPECT_EQ(parsed.io_binding, "test_biz.cabi.v1"); + EXPECT_EQ(parsed.io_binding, "test_biz.operator.v1"); // 2. 拒绝未知 schema_version nlohmann::json bad_ver = valid_cfg; bad_ver["schema_version"] = 2; EXPECT_FALSE( - DeploymentIoConfig::Parse(bad_ver, tmp_dir, "cabi", &parsed, &err)); + DeploymentIoConfig::Parse(bad_ver, tmp_dir, "operator", &parsed, &err)); // 3. 拒绝顶层未知字段 nlohmann::json bad_field = valid_cfg; bad_field["extra_field"] = "foo"; EXPECT_FALSE( - DeploymentIoConfig::Parse(bad_field, tmp_dir, "cabi", &parsed, &err)); + DeploymentIoConfig::Parse(bad_field, tmp_dir, "operator", &parsed, &err)); - // 4. C ABI 拒绝 outputs - nlohmann::json cabi_with_outputs = valid_cfg; - cabi_with_outputs["data"]["outputs"] = nlohmann::json::object(); - EXPECT_FALSE(DeploymentIoConfig::Parse(cabi_with_outputs, tmp_dir, "cabi", - &parsed, &err)); + // 4. 拒绝 cabi transport + EXPECT_FALSE( + DeploymentIoConfig::Parse(valid_cfg, tmp_dir, "cabi", &parsed, &err)); // 5. 路径逃逸拒绝 nlohmann::json escape_cfg = valid_cfg; escape_cfg["data"]["pipe_path"] = "../../../etc/passwd"; - EXPECT_FALSE( - DeploymentIoConfig::Parse(escape_cfg, tmp_dir, "cabi", &parsed, &err)); + EXPECT_FALSE(DeploymentIoConfig::Parse(escape_cfg, tmp_dir, "operator", + &parsed, &err)); fs::remove_all(tmp_dir); } @@ -308,21 +315,29 @@ TEST_F(IoBindingRegistryTest, StrictConfigDirectoryIsolationAndCwdInvariance) { std::string err; auto make_conf = [](const std::string& pipe) { - nlohmann::json cfg = { - {"schema_version", 1}, - {"data", {{"pipe_path", pipe}, {"io_binding", "test_biz.cabi.v1"}}}}; + nlohmann::json cfg = {{"schema_version", 1}, + {"data", + {{"pipe_path", pipe}, + {"io_binding", "test_biz.operator.v1"}, + {"outputs", + {{"answers", + {{"type", "answers"}, + {"meta_num", 0}, + {"metadata_type_id", 0}, + {"capacities", nlohmann::json::object()}}}}}}}}; return cfg; }; // 1. 同级文件 -> 成功 - EXPECT_TRUE(DeploymentIoConfig::Parse( - make_conf("pipeline.json"), base_dir.string(), "cabi", &parsed, &err)); + EXPECT_TRUE(DeploymentIoConfig::Parse(make_conf("pipeline.json"), + base_dir.string(), "operator", &parsed, + &err)); EXPECT_EQ(parsed.resolved_pipe_path, fs::canonical(base_dir / "pipeline.json").string()); // 2. 子目录文件 -> 成功 EXPECT_TRUE(DeploymentIoConfig::Parse(make_conf("subdir/sub_pipeline.json"), - base_dir.string(), "cabi", &parsed, + base_dir.string(), "operator", &parsed, &err)); EXPECT_EQ(parsed.resolved_pipe_path, fs::canonical(sub_dir / "sub_pipeline.json").string()); @@ -330,18 +345,18 @@ TEST_F(IoBindingRegistryTest, StrictConfigDirectoryIsolationAndCwdInvariance) { // 3. 父目录逃逸 (../outside/outside_pipeline.json) -> 严格拒绝 EXPECT_FALSE( DeploymentIoConfig::Parse(make_conf("../outside/outside_pipeline.json"), - base_dir.string(), "cabi", &parsed, &err)); + base_dir.string(), "operator", &parsed, &err)); EXPECT_NE(err.find("escapes config directory"), std::string::npos); // 4. 兄弟目录逃逸 (../sibling/sibling_pipeline.json) -> 严格拒绝 EXPECT_FALSE( DeploymentIoConfig::Parse(make_conf("../sibling/sibling_pipeline.json"), - base_dir.string(), "cabi", &parsed, &err)); + base_dir.string(), "operator", &parsed, &err)); EXPECT_NE(err.find("escapes config directory"), std::string::npos); // 5. 符号链接逃逸 (位于 base_dir 内但指向根外) -> 严格拒绝 EXPECT_FALSE(DeploymentIoConfig::Parse(make_conf("symlink_escape.json"), - base_dir.string(), "cabi", &parsed, + base_dir.string(), "operator", &parsed, &err)); EXPECT_NE(err.find("escapes config directory"), std::string::npos); @@ -359,8 +374,8 @@ TEST_F(IoBindingRegistryTest, StrictConfigDirectoryIsolationAndCwdInvariance) { DeploymentIoConfig cwd_parsed; std::string cwd_err; - bool read_ok = DeploymentIoConfig::ReadFromFile(conf_file.string(), "cabi", - &cwd_parsed, &cwd_err); + bool read_ok = DeploymentIoConfig::ReadFromFile( + conf_file.string(), "operator", &cwd_parsed, &cwd_err); // 恢复原工作目录 fs::current_path(orig_cwd, ec); @@ -378,11 +393,11 @@ TEST_F(IoBindingRegistryTest, FailClosedAuditRejectsInvalidUnselectedBinding) { // 注册合法暴露与绑定 IoBindingDefinition valid_binding; - valid_binding.binding_id = "test_biz.cabi.v1"; + valid_binding.binding_id = "test_biz.operator.v1"; valid_binding.biz_name = "test_biz_v1"; - valid_binding.transport = "cabi"; - valid_binding.input_converter_id = "test.in.cabi"; - valid_binding.output_converter_id = "test.out.cabi"; + valid_binding.transport = "operator"; + 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)); @@ -390,16 +405,16 @@ TEST_F(IoBindingRegistryTest, FailClosedAuditRejectsInvalidUnselectedBinding) { BizExposureDefinition exposure; exposure.biz_name = "test_biz_v1"; exposure.max_batch_size = 32; - exposure.required_transports = {"cabi"}; + exposure.required_transports = {"operator"}; EXPECT_TRUE(reg.RegisterExposure(exposure)); // 注册一个未被任何曝光引用的非法绑定 (输入端口缺少必需端口) IoBindingDefinition unselected_bad_binding; - unselected_bad_binding.binding_id = "unselected_bad.cabi.v1"; + unselected_bad_binding.binding_id = "unselected_bad.operator.v1"; unselected_bad_binding.biz_name = "test_biz_v1"; - unselected_bad_binding.transport = "cabi"; - unselected_bad_binding.input_converter_id = "test.in.cabi"; - unselected_bad_binding.output_converter_id = "test.out.cabi"; + unselected_bad_binding.transport = "operator"; + unselected_bad_binding.input_converter_id = "test.in.operator"; + unselected_bad_binding.output_converter_id = "test.out.operator"; // 故意遗漏必需输入映射 texts unselected_bad_binding.input_ports = {}; unselected_bad_binding.output_ports = {{"answers", "llm_answers"}}; diff --git a/tests/unit/adapter/test_io_converters.cpp b/tests/unit/adapter/test_io_converters.cpp index ad18cd13..20b8ad34 100644 --- a/tests/unit/adapter/test_io_converters.cpp +++ b/tests/unit/adapter/test_io_converters.cpp @@ -22,39 +22,39 @@ int DummyEncode(AlgContext*, const OutputPortBindings&, } // namespace +struct ReproA { + int a = 7; +}; +struct ReproB { + uint64_t b = 42; +}; +DECLARE_EXTERNAL_TYPE_TRAITS(ReproA, "ReproA"); +DECLARE_EXTERNAL_TYPE_TRAITS(ReproB, "ReproB"); + TEST(IoConverterTest, ViewAccessorsAndPortBindings) { - // 1. ExternalInputBatchView C ABI 与 Slot 访问 + // 1. ExternalInputBatchView Slot 访问 ExternalInputBatchView in_view; int sample_int = 42; - const void* items[] = {&sample_int}; - in_view.items = items; in_view.count = 1; - in_view.type_id = "int"; - - EXPECT_EQ(in_view.GetCAbi(0), &sample_int); - EXPECT_EQ(in_view.GetCAbi(1), nullptr); - EXPECT_EQ(in_view.GetCAbi(0), nullptr); - EXPECT_EQ(in_view.At(0), &sample_int); - - auto shared_sample = std::make_shared(100); - in_view.slots["slot_a"] = {shared_sample}; + in_view.leased_slots["slot_a"] = {&sample_int}; in_view.slot_types["slot_a"] = "int"; - EXPECT_EQ(in_view.GetSlot("slot_a", 0), shared_sample.get()); + + EXPECT_EQ(in_view.GetSlot("slot_a", 0), &sample_int); EXPECT_EQ(in_view.GetSlot("slot_a", 0), nullptr); EXPECT_EQ(in_view.GetSlot("slot_a", 1), nullptr); EXPECT_EQ(in_view.GetSlot("unknown", 0), nullptr); - EXPECT_EQ(in_view.At(0, "slot_a"), shared_sample.get()); + + auto shared_sample = std::make_shared(100); + in_view.slots["slot_shared"] = {shared_sample}; + in_view.slot_types["slot_shared"] = "int"; + EXPECT_EQ(in_view.GetSlot("slot_shared", 0), shared_sample.get()); + EXPECT_EQ(in_view.GetSlot("slot_shared", 0), nullptr); + EXPECT_EQ(in_view.GetSlot("slot_shared", 1), nullptr); // 2. ExternalOutputBatchView 访问 ExternalOutputBatchView out_view; int out_sample = 0; - void* out_items[] = {&out_sample}; - out_view.items = out_items; out_view.count = 1; - out_view.type_id = "int"; - EXPECT_EQ(out_view.GetCAbi(0), &out_sample); - EXPECT_EQ(out_view.GetCAbi(0), nullptr); - out_view.leased_slots["out_slot"] = {&out_sample}; out_view.slot_types["out_slot"] = "int"; out_view.slot_capacities["out_slot"]["field_1"] = 1024; @@ -84,23 +84,23 @@ TEST(IoConverterTest, RegisterAndFindInputConverter) { auto& reg = IoConverterRegistry::Instance(); InputConverterDefinition def; - def.converter_id = "test.input.cabi.v1"; - def.transport = "cabi"; + def.converter_id = "test.input.operator.v1"; + def.transport = "operator"; def.schema_id = "test_input"; def.schema_version = 1; def.external_type = "int"; - def.external_slots = { - ExternalSlotDefinition("inputs", "int", PortDirection::kInput, true)}; + def.external_slots = {ExternalSlotDefinition( + "inputs", "int", PortDirection::kInput, true, "int", "inputs")}; def.max_batch_size = 64; def.logical_ports = {NodePortDefinition("texts", "TextBatch", true, "1:1")}; def.decode_fn = &DummyDecode; EXPECT_TRUE(reg.RegisterInputConverter(def)); - const auto* found = reg.FindInputConverter("test.input.cabi.v1"); + const auto* found = reg.FindInputConverter("test.input.operator.v1"); ASSERT_NE(found, nullptr); - EXPECT_EQ(found->converter_id, "test.input.cabi.v1"); - EXPECT_EQ(found->transport, "cabi"); + EXPECT_EQ(found->converter_id, "test.input.operator.v1"); + EXPECT_EQ(found->transport, "operator"); EXPECT_EQ(found->logical_ports.size(), 1U); // 重复注册拒绝并记录冲突 @@ -112,23 +112,23 @@ TEST(IoConverterTest, RegisterAndFindOutputConverter) { auto& reg = IoConverterRegistry::Instance(); OutputConverterDefinition def; - def.converter_id = "test.output.cabi.v1"; - def.transport = "cabi"; + def.converter_id = "test.output.operator.v1"; + def.transport = "operator"; def.schema_id = "test_output"; def.schema_version = 1; def.external_type = "int"; - def.external_slots = { - ExternalSlotDefinition("answers", "int", PortDirection::kOutput, true)}; + def.external_slots = {ExternalSlotDefinition( + "answers", "int", PortDirection::kOutput, true, "int", "answers")}; def.max_batch_size = 64; def.logical_ports = {NodePortDefinition("answers", "TextBatch", true, "1:1")}; def.encode_fn = &DummyEncode; EXPECT_TRUE(reg.RegisterOutputConverter(def)); - const auto* found = reg.FindOutputConverter("test.output.cabi.v1"); + const auto* found = reg.FindOutputConverter("test.output.operator.v1"); ASSERT_NE(found, nullptr); - EXPECT_EQ(found->converter_id, "test.output.cabi.v1"); - EXPECT_EQ(found->transport, "cabi"); + EXPECT_EQ(found->converter_id, "test.output.operator.v1"); + EXPECT_EQ(found->transport, "operator"); } TEST(IoConverterTest, RejectsInvalidDefinitions) { @@ -142,14 +142,17 @@ TEST(IoConverterTest, RejectsInvalidDefinitions) { bad_in.schema_id = "test"; bad_in.schema_version = 1; bad_in.external_type = "int"; - bad_in.external_slots = { - ExternalSlotDefinition("inputs", "int", PortDirection::kInput, true)}; + bad_in.external_slots = {ExternalSlotDefinition( + "inputs", "int", PortDirection::kInput, true, "int", "inputs")}; bad_in.max_batch_size = 64; bad_in.logical_ports = { NodePortDefinition("texts", "TextBatch", true, "1:1")}; EXPECT_FALSE(IoConverterRegistry::Instance().RegisterInputConverter(bad_in)); - bad_in.transport = "cabi"; + bad_in.transport = "cabi"; // cabi must be rejected! + EXPECT_FALSE(IoConverterRegistry::Instance().RegisterInputConverter(bad_in)); + + bad_in.transport = "operator"; bad_in.decode_fn = nullptr; EXPECT_FALSE(IoConverterRegistry::Instance().RegisterInputConverter(bad_in)); @@ -174,8 +177,8 @@ TEST(IoConverterTest, RejectsInvalidDefinitions) { EXPECT_FALSE(IoConverterRegistry::Instance().RegisterInputConverter(bad_in)); // 缺少 logical_ports - bad_in.external_slots = { - ExternalSlotDefinition("inputs", "int", PortDirection::kInput, true)}; + bad_in.external_slots = {ExternalSlotDefinition( + "inputs", "int", PortDirection::kInput, true, "int", "inputs")}; bad_in.logical_ports.clear(); EXPECT_FALSE(IoConverterRegistry::Instance().RegisterInputConverter(bad_in)); @@ -187,12 +190,12 @@ TEST(IoConverterTest, RejectsInvalidDefinitions) { OutputConverterDefinition bad_out; bad_out.converter_id = "bad.out"; - bad_out.transport = "cabi"; + bad_out.transport = "operator"; bad_out.schema_id = "test"; bad_out.schema_version = 1; bad_out.external_type = "int"; - bad_out.external_slots = { - ExternalSlotDefinition("answers", "int", PortDirection::kOutput, true)}; + bad_out.external_slots = {ExternalSlotDefinition( + "answers", "int", PortDirection::kOutput, true, "int", "answers")}; bad_out.max_batch_size = 64; bad_out.logical_ports = { NodePortDefinition("answers", "TextBatch", true, "1:1")}; @@ -201,4 +204,120 @@ TEST(IoConverterTest, RejectsInvalidDefinitions) { IoConverterRegistry::Instance().RegisterOutputConverter(bad_out)); } +TEST(IoConverterTest, ExactSlotLookupAndTypeSafety) { + // 1. shared_ptr (slots) path + ExternalInputBatchView v; + v.count = 1; + v.type_id = "ReproA"; + auto b_ptr = std::make_shared(); + v.slots["channel.slot"] = {b_ptr}; + v.slot_types["channel.slot"] = "ReproB"; + + // Exact lookup with correct type succeeds + EXPECT_EQ(v.GetSlot("channel.slot", 0), b_ptr.get()); + // Exact lookup with wrong type returns nullptr + EXPECT_EQ(v.GetSlot("channel.slot", 0), nullptr); + // Short suffix lookup must NOT fallback: returns nullptr + EXPECT_EQ(v.GetSlot("slot", 0), nullptr); + EXPECT_EQ(v.GetSlot("slot", 0), nullptr); + + // 2. leased_slots path + ExternalInputBatchView v_leased; + v_leased.count = 1; + v_leased.type_id = "ReproA"; + ReproB raw_b; + v_leased.leased_slots["channel.slot"] = {&raw_b}; + v_leased.slot_types["channel.slot"] = "ReproB"; + + // Exact lookup with correct type succeeds + EXPECT_EQ(v_leased.GetSlot("channel.slot", 0), &raw_b); + // Exact lookup with wrong type returns nullptr + EXPECT_EQ(v_leased.GetSlot("channel.slot", 0), nullptr); + // Short suffix lookup must NOT fallback: returns nullptr + EXPECT_EQ(v_leased.GetSlot("slot", 0), nullptr); + EXPECT_EQ(v_leased.GetSlot("slot", 0), nullptr); + + // 3. Multi-slot ambiguity resolution: distinct logical slots of same type + ExternalInputBatchView multi; + multi.count = 1; + auto left = std::make_shared(); + left->a = 10; + auto right = std::make_shared(); + right->a = 20; + multi.slots["left"] = {left}; + multi.slots["right"] = {right}; + multi.slot_types["left"] = "ReproA"; + multi.slot_types["right"] = "ReproA"; + + EXPECT_EQ(multi.GetSlot("left", 0), left.get()); + EXPECT_EQ(multi.GetSlot("right", 0), right.get()); + EXPECT_EQ(multi.GetSlot("left", 0)->a, 10); + EXPECT_EQ(multi.GetSlot("right", 0)->a, 20); + EXPECT_EQ(multi.GetSlot("unknown", 0), nullptr); +} + +TEST(IoConverterTest, + RejectsEmptyTypeSuffixAndSupportsMultipleSlotsOfSameType) { + auto& reg = IoConverterRegistry::Instance(); + + // 1. Input converter with empty type_suffix must be rejected + InputConverterDefinition bad_in; + bad_in.converter_id = "test.empty_suffix.in"; + bad_in.transport = "operator"; + bad_in.schema_id = "test_schema"; + bad_in.schema_version = 1; + bad_in.external_type = "int"; + bad_in.external_slots = {ExternalSlotDefinition( + "slot1", "int", PortDirection::kInput, true, "int", "")}; + bad_in.max_batch_size = 64; + bad_in.logical_ports = { + NodePortDefinition("texts", "TextBatch", true, "1:1")}; + bad_in.decode_fn = &DummyDecode; + + EXPECT_FALSE(reg.RegisterInputConverter(bad_in)); + + // 2. Output converter with empty type_suffix must be rejected + OutputConverterDefinition bad_out; + bad_out.converter_id = "test.empty_suffix.out"; + bad_out.transport = "operator"; + bad_out.schema_id = "test_schema"; + bad_out.schema_version = 1; + bad_out.external_type = "int"; + bad_out.external_slots = {ExternalSlotDefinition( + "slot1", "int", PortDirection::kOutput, true, "int", "")}; + bad_out.max_batch_size = 64; + bad_out.logical_ports = { + NodePortDefinition("answers", "TextBatch", true, "1:1")}; + bad_out.encode_fn = &DummyEncode; + + EXPECT_FALSE(reg.RegisterOutputConverter(bad_out)); + + // 3. Multiple slots of same ValueType with distinct slot names can register + // successfully + InputConverterDefinition multi_in; + multi_in.converter_id = "test.multi_slot.in"; + multi_in.transport = "operator"; + multi_in.schema_id = "test_schema"; + multi_in.schema_version = 1; + multi_in.external_type = "int"; + multi_in.external_slots = { + ExternalSlotDefinition("slot_first", "int", PortDirection::kInput, true, + "int", "int_suffix"), + ExternalSlotDefinition("slot_second", "int", PortDirection::kInput, true, + "int", "int_suffix")}; + multi_in.max_batch_size = 64; + multi_in.logical_ports = { + NodePortDefinition("texts", "TextBatch", true, "1:1")}; + multi_in.decode_fn = &DummyDecode; + + EXPECT_TRUE(reg.RegisterInputConverter(multi_in)); + const auto* found_in = reg.FindInputConverter("test.multi_slot.in"); + ASSERT_NE(found_in, nullptr); + EXPECT_EQ(found_in->external_slots.size(), 2U); + EXPECT_EQ(found_in->external_slots[0].slot_name, "slot_first"); + EXPECT_EQ(found_in->external_slots[1].slot_name, "slot_second"); + EXPECT_EQ(found_in->external_slots[0].type_suffix, "int_suffix"); + EXPECT_EQ(found_in->external_slots[1].type_suffix, "int_suffix"); +} + } // namespace llm_edgeflow diff --git a/tests/unit/adapter/test_text_converters.cpp b/tests/unit/adapter/test_text_converters.cpp index 3b0c8c47..c1626341 100644 --- a/tests/unit/adapter/test_text_converters.cpp +++ b/tests/unit/adapter/test_text_converters.cpp @@ -1,5 +1,7 @@ #include +#include + #include "adapter/adapter_status.h" #include "adapter/converter_authoring.h" #include "adapter/io_binding_registry.h" @@ -7,31 +9,39 @@ #include "adapter/io_converter_registry.h" #include "contracts/inference_payloads.h" #include "core/alg_context.h" -#include "edgeflow/c_api.h" #include "edgeflow/operator/types.h" +#include "platform_mock/operator_data_types.h" namespace llm_edgeflow { class TextConvertersTest : public ::testing::Test {}; -TEST_F(TextConvertersTest, TextPlainCAbiInputDecodeSuccess) { - const auto* conv = - IoConverterRegistry::Instance().FindInputConverter("text.plain.cabi.v1"); +TEST_F(TextConvertersTest, TextPlainOperatorInputDecodeSuccess) { + const auto* conv = IoConverterRegistry::Instance().FindInputConverter( + "text.plain.operator.v1"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->decode_fn, nullptr); - CompanyEntityInputStruct s1{1001, "Hello world"}; - CompanyEntityInputStruct s2{1002, "Second sentence"}; - const void* items[] = {&s1, &s2}; + std::string text1 = "Hello world"; + std::string text2 = "Second sentence"; + CompanyString cs1{static_cast(text1.size()), + const_cast(text1.data())}; + CompanyString cs2{static_cast(text2.size()), + const_cast(text2.data())}; + + CompanyOperatorEntityInput s1{1001, &cs1}; + CompanyOperatorEntityInput s2{1002, &cs2}; ExternalInputBatchView view; - view.items = items; view.count = 2; + view.leased_slots["entity_in"] = {&s1, &s2}; + view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; InputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions options; options.converter_id = conv->converter_id; + options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -53,23 +63,26 @@ TEST_F(TextConvertersTest, TextPlainCAbiInputDecodeSuccess) { TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { const auto* conv = IoConverterRegistry::Instance().FindInputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->decode_fn, nullptr); // 1. Valid JSON with query field - CompanyEntityInputStruct valid_s{ - 2001, "{\"query\": \"Translate me!\", \"lang\": \"en\"}"}; - const void* valid_items[] = {&valid_s}; + std::string valid_json = "{\"query\": \"Translate me!\", \"lang\": \"en\"}"; + CompanyString cs_valid{static_cast(valid_json.size()), + const_cast(valid_json.data())}; + CompanyOperatorEntityInput valid_s{2001, &cs_valid}; ExternalInputBatchView valid_view; - valid_view.items = valid_items; valid_view.count = 1; + valid_view.leased_slots["entity_in"] = {&valid_s}; + valid_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; InputPortBindings bindings({{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}); InputDecodeOptions options; options.converter_id = conv->converter_id; + options.transport = "operator"; AlgContext ctx; AdapterStatus status; @@ -82,11 +95,15 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { EXPECT_EQ((*sentences)[0].data, "Translate me!"); // 2. Invalid JSON without query - CompanyEntityInputStruct invalid_s{2002, "{\"text\": \"No query field\"}"}; - const void* invalid_items[] = {&invalid_s}; + std::string invalid_json = "{\"text\": \"No query field\"}"; + CompanyString cs_invalid{static_cast(invalid_json.size()), + const_cast(invalid_json.data())}; + CompanyOperatorEntityInput invalid_s{2002, &cs_invalid}; + ExternalInputBatchView invalid_view; - invalid_view.items = invalid_items; invalid_view.count = 1; + invalid_view.leased_slots["entity_in"] = {&invalid_s}; + invalid_view.slot_types["entity_in"] = "CompanyOperatorEntityInput"; AlgContext bad_ctx; AdapterStatus bad_status; @@ -95,9 +112,9 @@ TEST_F(TextConvertersTest, TranslateJsonInputDecodeValidAndInvalid) { EXPECT_EQ(bad_status.FieldPath(), "json"); } -TEST_F(TextConvertersTest, TranslationJsonOutputEncodeCAbi) { +TEST_F(TextConvertersTest, TranslationJsonOutputEncodeOperator) { const auto* conv = IoConverterRegistry::Instance().FindOutputConverter( - "translate.json.cabi.v1"); + "translate.json.operator.v1"); ASSERT_NE(conv, nullptr); ASSERT_NE(conv->encode_fn, nullptr); @@ -107,17 +124,21 @@ TEST_F(TextConvertersTest, TranslationJsonOutputEncodeCAbi) { ctx.Publish("raw_request_ids", req_ids); ctx.Publish("llm_answers", answers); - CompanyEntityOutputStruct out_struct{}; - void* out_ptrs[] = {&out_struct}; + char buf[2048] = {0}; + CompanyString cs_buf{2047, buf}; + CompanyOperatorEntityOutput out_struct{}; + out_struct.entities_json = &cs_buf; ExternalOutputBatchView dest; - dest.items = out_ptrs; - dest.capacity = 1; + dest.count = 1; + dest.leased_slots["entity_out"] = {&out_struct}; + dest.slot_types["entity_out"] = "CompanyOperatorEntityOutput"; OutputPortBindings bindings( {{"raw_request_ids", "raw_request_ids"}, {"llm_answers", "llm_answers"}}); OutputEncodeOptions options; options.converter_id = conv->converter_id; + options.transport = "operator"; size_t written = 0; AdapterStatus status; @@ -127,46 +148,51 @@ TEST_F(TextConvertersTest, TranslationJsonOutputEncodeCAbi) { EXPECT_EQ(out_struct.request_id, 3001U); EXPECT_EQ(out_struct.status_code, 0); - auto parsed = nlohmann::json::parse(out_struct.entities_json); + ASSERT_NE(out_struct.entities_json, nullptr); + std::string json_res(out_struct.entities_json->data, + out_struct.entities_json->length); + auto parsed = nlohmann::json::parse(json_res); EXPECT_EQ(parsed["translated"], "Bonjour le monde"); } TEST_F(TextConvertersTest, ProductionBindingsUseDeclaredHostTypes) { const auto* entity_binding = - IoBindingRegistry::Instance().FindBinding("entity_extract.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); ASSERT_NE(entity_binding, nullptr); const auto* keyword_binding = - IoBindingRegistry::Instance().FindBinding("keyword_match.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("keyword_match.operator.v1"); ASSERT_NE(keyword_binding, nullptr); - // Entity extract uses CompanyEntityInputStruct via text.plain.cabi.v1 - EXPECT_EQ(entity_binding->input_converter_id, "text.plain.cabi.v1"); + // Entity extract uses CompanyOperatorEntityInput via text.plain.operator.v1 + EXPECT_EQ(entity_binding->input_converter_id, "text.plain.operator.v1"); const auto* entity_conv = IoConverterRegistry::Instance().FindInputConverter( entity_binding->input_converter_id); ASSERT_NE(entity_conv, nullptr); - EXPECT_EQ(entity_conv->external_type, "CompanyEntityInputStruct"); + EXPECT_EQ(entity_conv->external_type, "CompanyOperatorEntityInput"); - // Keyword match uses CompanyKeywordInputStruct via keyword.plain.cabi.v1 - EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.cabi.v1"); + // Keyword match uses CompanyOperatorKeywordInput via + // keyword.plain.operator.v1 + EXPECT_EQ(keyword_binding->input_converter_id, "keyword.plain.operator.v1"); const auto* keyword_conv = IoConverterRegistry::Instance().FindInputConverter( keyword_binding->input_converter_id); ASSERT_NE(keyword_conv, nullptr); - EXPECT_EQ(keyword_conv->external_type, "CompanyKeywordInputStruct"); + EXPECT_EQ(keyword_conv->external_type, "CompanyOperatorKeywordInput"); // Output converters are distinct - EXPECT_EQ(entity_binding->output_converter_id, "document.structured.cabi.v1"); - EXPECT_EQ(keyword_binding->output_converter_id, "keyword.result.cabi.v1"); + EXPECT_EQ(entity_binding->output_converter_id, + "document.structured.operator.v1"); + EXPECT_EQ(keyword_binding->output_converter_id, "keyword.result.operator.v1"); } TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { // 证明同一个转换器 ID 可以在不同绑定间复用:通过测试专用绑定 IoBindingDefinition test_reuse_binding; - test_reuse_binding.binding_id = "test_text_reuse.cabi.v1"; + test_reuse_binding.binding_id = "test_text_reuse.operator.v1"; test_reuse_binding.biz_name = "entity_extract_v1"; - test_reuse_binding.transport = "cabi"; - test_reuse_binding.input_converter_id = "text.plain.cabi.v1"; - test_reuse_binding.output_converter_id = "document.structured.cabi.v1"; + test_reuse_binding.transport = "operator"; + 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 = {{"raw_request_ids", "raw_request_ids"}, {"input_sentences", "input_sentences"}}; test_reuse_binding.output_ports = { @@ -177,9 +203,9 @@ TEST_F(TextConvertersTest, InputConverterReusedAcrossTestBindings) { IoBindingRegistry::Instance().RegisterBinding(test_reuse_binding); const auto* b1 = - IoBindingRegistry::Instance().FindBinding("entity_extract.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("entity_extract.operator.v1"); const auto* b2 = - IoBindingRegistry::Instance().FindBinding("test_text_reuse.cabi.v1"); + IoBindingRegistry::Instance().FindBinding("test_text_reuse.operator.v1"); ASSERT_NE(b1, nullptr); ASSERT_NE(b2, nullptr); EXPECT_EQ(b1->input_converter_id, b2->input_converter_id); diff --git a/tests/unit/engine/test_engine_fault_tolerance_and_lifecycle.cpp b/tests/unit/engine/test_engine_fault_tolerance_and_lifecycle.cpp index 36f714bd..997469db 100644 --- a/tests/unit/engine/test_engine_fault_tolerance_and_lifecycle.cpp +++ b/tests/unit/engine/test_engine_fault_tolerance_and_lifecycle.cpp @@ -13,10 +13,11 @@ #include "core/node_interface.h" #include "core/node_registry.h" #include "core/pipeline.h" -#include "edgeflow/c_api.h" -#include "edgeflow/c_api.hpp" +#include "edgeflow/operator/interface.h" +#include "edgeflow/operator/types.h" #include "engine/fixed_batch_executor.h" #include "engine/model_interface.h" +#include "platform_mock/operator_data_types.h" static std::string GetConfigPath(const std::string& rel_path) { FILE* fp = fopen(rel_path.c_str(), "r"); @@ -316,32 +317,41 @@ TEST_F(EngineFaultToleranceAndLifecycleTest, LargePayloadRaiiDestruction) { // 4. 全局生命周期高频循环初始化与销毁压测 (30 Cycles) TEST_F(EngineFaultToleranceAndLifecycleTest, RapidGlobalLifecycleInitDeInit) { - std::string cfg_path = - GetConfigPath("configs/pipeline_keyword_match_cabi.json"); + using namespace llm_edgeflow; + auto op = operator_api::Get_LLM_EDGEFLOW_OperatorTable(); for (int cycle = 0; cycle < 30; ++cycle) { - EXPECT_EQ(Alg_Init(), 0); + EXPECT_EQ(op.Init(), 0); - CompanyAlgParamCreate param; - param.config_file_path = cfg_path.c_str(); - param.model_root_dir = "./models"; + operator_api::CreateParam param{}; + param.model_path = "."; + param.cfg_file_name = "configs/pipeline_keyword_match_rules.conf"; param.device_id = 0; + param.compute_platform = operator_api::ComputePlatform::kCpu; + param.max_frame_depth = 25; void* handle = nullptr; - ASSERT_EQ(Alg_Create(&handle, ¶m), 0); + ASSERT_EQ(op.Create(&handle, ¶m), 0); ASSERT_NE(handle, nullptr); - const char* input_text = "循环生命周期压测文本"; - CompanyKeywordInputStruct in_req{static_cast(10000 + cycle), - input_text}; - std::vector inputs = {&in_req}; - CompanyKeywordOutputStruct out_res; - std::vector outputs = {&out_res}; + std::string sentence = "循环生命周期压测文本"; + CompanyString cs_sentence{static_cast(sentence.size()), + const_cast(sentence.data())}; + CompanyOperatorKeywordInput in_req{}; + in_req.request_id = static_cast(10000 + cycle); + in_req.sentence_text = &cs_sentence; + + operator_api::NamedIoBatch inputs(1); + inputs[0]["client_channel.keyword_in"] = + operator_api::MakeBorrowedOperatorInput(&in_req); + operator_api::NamedIoBatch outputs(1); + outputs[0]["client_channel.keyword_out"] = std::shared_ptr(); - int ret = Alg_Process(handle, inputs, outputs); + int ret = op.Process(handle, inputs, outputs); EXPECT_EQ(ret, 0); - EXPECT_EQ(Alg_Destroy(handle), 0); - EXPECT_EQ(Alg_DeInit(), 0); + outputs.clear(); + EXPECT_EQ(op.Destroy(handle), 0); + EXPECT_EQ(op.Deinit(), 0); } } diff --git a/tests/unit/engine/test_onnx_and_embedding_model.cpp b/tests/unit/engine/test_onnx_and_embedding_model.cpp index efdb3f9c..d6833b7e 100644 --- a/tests/unit/engine/test_onnx_and_embedding_model.cpp +++ b/tests/unit/engine/test_onnx_and_embedding_model.cpp @@ -20,7 +20,6 @@ #include "core/pipeline_catalog.h" #include "core/session_context.h" #include "dev_support/inference/bge_model_test_support.h" -#include "edgeflow/c_api.h" #include "engine/backend_interface.h" #include "engine/backend_registry.h" #include "engine/backends/onnxruntime/onnxruntime_backend.h" diff --git a/tests/unit/engine/test_onnx_and_reranker_model.cpp b/tests/unit/engine/test_onnx_and_reranker_model.cpp index 7f97f21e..7359ca31 100644 --- a/tests/unit/engine/test_onnx_and_reranker_model.cpp +++ b/tests/unit/engine/test_onnx_and_reranker_model.cpp @@ -21,7 +21,6 @@ #include "core/pipeline_validator.h" #include "core/session_context.h" #include "dev_support/inference/bge_model_test_support.h" -#include "edgeflow/c_api.h" #include "engine/backend_interface.h" #include "engine/backend_registry.h" #include "engine/backends/onnxruntime/onnxruntime_backend.h" diff --git a/tests/unit/nodes/test_text_rerank_node.cpp b/tests/unit/nodes/test_text_rerank_node.cpp index 638c9538..4c95a492 100644 --- a/tests/unit/nodes/test_text_rerank_node.cpp +++ b/tests/unit/nodes/test_text_rerank_node.cpp @@ -12,6 +12,7 @@ #include "core/node_registry.h" #include "core/pipeline_validator.h" #include "core/session_context.h" +#include "edgeflow/operator/interface.h" #include "engine/model_interface.h" #include "nodes/node_error_codes.h" #include "tests/support/node_test_utils.h" @@ -326,7 +327,7 @@ class ControllableMockRerankModel : public IRerankModel { class TextRerankRankingTest : public ::testing::Test { protected: void SetUp() override { - Alg_Init(); + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Init(); mock_model_ = std::make_shared(); session_ctx_.GetModelManager().RegisterModel("test_rerank_model", mock_model_, "test-v1"); @@ -334,7 +335,9 @@ class TextRerankRankingTest : public ::testing::Test { ASSERT_NE(node_, nullptr); } - void TearDown() override { Alg_DeInit(); } + void TearDown() override { + operator_api::Get_LLM_EDGEFLOW_OperatorTable().Deinit(); + } std::shared_ptr mock_model_; SessionContext session_ctx_; diff --git a/tests/unit/operator/test_operator_value_registry.cpp b/tests/unit/operator/test_operator_value_registry.cpp index 14d1a321..80c73a91 100644 --- a/tests/unit/operator/test_operator_value_registry.cpp +++ b/tests/unit/operator/test_operator_value_registry.cpp @@ -1122,11 +1122,11 @@ TEST(OperatorValueRegistryTest, EXPECT_EQ(binding->validate_external(&valid_audio, limits, &err), 0); } -TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnChannelNameBoundaries) { +TEST(OperatorValueRegistryTest, OperatorAgreesOnChannelNameBoundaries) { const auto* binding = OperatorValueTypeRegistry::Instance().GetBindingBySuffix("audit_in"); - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audit.plain.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audit.plain.operator.v1"); ASSERT_NE(binding, nullptr); ASSERT_NE(in_conv, nullptr); std::string query = "hello"; @@ -1135,15 +1135,13 @@ TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnChannelNameBoundaries) { std::string channel(length < 0 ? 0 : length, 'c'); CompanyString named_channel{static_cast(channel.size()), channel.data()}; - CompanyAuditInputStruct c_input{7, query.c_str(), - length < 0 ? nullptr : channel.c_str()}; CompanyOperatorAuditInput op_input{7, &text, length < 0 ? nullptr : &named_channel}; - const void* inputs[]{&c_input}; AlgContext ctx; const bool expected = length <= 256; ExternalInputBatchView view; - view.items = inputs; + view.leased_slots["audit_in"] = {&op_input}; + view.slot_types["audit_in"] = "CompanyOperatorAuditInput"; view.count = 1; view.type_id = in_conv->external_type; InputPortBindings port_bindings({{"raw_request_ids", "raw_request_ids"}, @@ -1151,7 +1149,7 @@ TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnChannelNameBoundaries) { {"channel_names", "channel_names"}}); InputDecodeOptions options; options.converter_id = in_conv->converter_id; - options.transport = "cabi"; + options.transport = "operator"; options.max_batch_size = 64; int dec_ret = in_conv->decode_fn(view, options, port_bindings, &ctx, nullptr); @@ -1161,11 +1159,11 @@ TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnChannelNameBoundaries) { } } -TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnPcmBoundaries) { +TEST(OperatorValueRegistryTest, OperatorAgreesOnPcmBoundaries) { const auto* binding = OperatorValueTypeRegistry::Instance().GetBindingBySuffix("audio_in"); - const auto* in_conv = - IoConverterRegistry::Instance().FindInputConverter("audio.pcm.cabi.v1"); + const auto* in_conv = IoConverterRegistry::Instance().FindInputConverter( + "audio.pcm.operator.v1"); ASSERT_NE(binding, nullptr); ASSERT_NE(in_conv, nullptr); std::vector samples(biz_input::kMaxAudioPcmSamples, 0); @@ -1188,24 +1186,19 @@ TEST(OperatorValueRegistryTest, CAndOperatorAgreeOnPcmBoundaries) { {biz_input::kMaxAudioPcmSamples, 16000, true, true}, {biz_input::kMaxAudioPcmSamples + 1, 16000, true, false}}; for (const auto& test : cases) { - CompanyAudioInputStruct c_input{}; - c_input.request_id = 7; - c_input.pcm_length = test.length; - c_input.sample_rate = test.rate; - c_input.pcm_buffer = test.has_buffer ? samples.data() : nullptr; - CompanyOperatorAudioInput op_input{7, c_input.pcm_buffer, test.length, - test.rate}; - const void* inputs[]{&c_input}; + CompanyOperatorAudioInput op_input{ + 7, test.has_buffer ? samples.data() : nullptr, test.length, test.rate}; AlgContext ctx; ExternalInputBatchView view; - view.items = inputs; + view.leased_slots["audio_in"] = {&op_input}; + view.slot_types["audio_in"] = "CompanyOperatorAudioInput"; view.count = 1; view.type_id = in_conv->external_type; InputPortBindings port_bindings({{"raw_request_ids", "raw_request_ids"}, {"audio_inputs", "audio_inputs"}}); InputDecodeOptions options; options.converter_id = in_conv->converter_id; - options.transport = "cabi"; + options.transport = "operator"; options.max_batch_size = 64; int dec_ret = in_conv->decode_fn(view, options, port_bindings, &ctx, nullptr);