diff --git a/.agents/skills/edgeflow-node-developer/SKILL.md b/.agents/skills/edgeflow-node-developer/SKILL.md index 9b3ca7ed..05acb5fa 100644 --- a/.agents/skills/edgeflow-node-developer/SKILL.md +++ b/.agents/skills/edgeflow-node-developer/SKILL.md @@ -26,6 +26,7 @@ description: 新增或修改 LLM-EdgeFlow 的 common/custom Node。所有 Node | 实际需求 | 起点 | | --- | --- | | 逐项文本变换,数量与来源不变 | `tools/scaffold_custom_node.py --write-test`;`Run` 用 `MapPayloads` 调用 `Transform` | +| 给现有 Node 增加普通配置项 | [教程第 2 节](../../../doc/dev_guide/first_custom_node.md#2-为同一个节点增加普通配置):添加 `Params` 与字段声明;需要运行时更新时再加 Control | | 文本前处理 → 一次 LLM → 文本后处理 | 同一命令加 `-m llm`:多出 `Models`、`BuildPrompt` / `FormatAnswer`;生成参数由 `GenerateParameters` 从配置读取 | | 同时生成可运行方案 | [text-llm-node Recipe](../../../doc/dev_guide/recipe_text_llm_node.md),不要再单独运行脚手架 | | 1:1 的 Embedding/ASR/OCR/Rerank | 同一命令加 `-m `;检查生成端口是否符合真实算法 | diff --git a/doc/README.md b/doc/README.md index ef6a6376..dc8878b8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -10,7 +10,7 @@ | 路径 | 何时使用 | 阅读入口 | | --- | --- | --- | | 已有能力编排 | 外部契约不变,调整配置与连线 | [Studio 编排练习](../tools/pipeline_studio/README.md#第一次编排) → [运行当前方案](../tools/pipeline_studio/README.md#运行当前方案);提示词任务可用[现有 Recipe](dev_guide/recipe_prompt_config.md) | -| 新增自定义 Node | 需要实现缺失算法 | [自定义 Node 入门](dev_guide/first_custom_node.md),按任务选择 Recipe 或普通脚手架 | +| 新增自定义 Node | 需要实现缺失算法 | [自定义 Node 入门](dev_guide/first_custom_node.md):同一个节点从文本处理、普通配置到调用模型和接入 Pipeline | | 新增外部业务契约 | 请求或响应的字段、格式、语义变化 | [业务接入指南](dev_guide/business_onboarding.md),先复用已有转换器、载体和 Demo 运行代码 | ## 使用开发 Skills diff --git a/doc/dev_guide/custom_node_concepts.md b/doc/dev_guide/custom_node_concepts.md index 9ebe3b4c..fbed7ab0 100644 --- a/doc/dev_guide/custom_node_concepts.md +++ b/doc/dev_guide/custom_node_concepts.md @@ -62,8 +62,8 @@ Node 是批量处理函数。输入端口对应参数,输出端口对应返回 | 名字 | 练习中的例子 | 谁决定它 | | --- | --- | --- | -| 节点类型 `type` | `my_business_llm` | C++ 注册;说明“这是什么操作” | -| 节点实例 `name` | `generate_entities` | Pipeline;说明“这次使用这个操作的位置” | +| 节点类型 `type` | `my_text_normalize` | C++ 注册;说明“这是什么操作” | +| 节点实例 `name` | `normalize` | Pipeline;说明“这次使用这个操作的位置” | | 逻辑端口 | `input`、`output` | Node;说明“操作接收、返回哪些值” | 同一个类型可以在一个方案出现多次,只需使用不同的 `name`。 @@ -71,11 +71,11 @@ Node 是批量处理函数。输入端口对应参数,输出端口对应返回 Pipeline 的 `inputs` 直接引用来源。例如: ```json -{"type":"my_business_llm", "name":"generate_entities", "params":{"bind_model":"entity_llm"}, - "inputs":{"input":"input.sentence_text"}} +{"type":"my_text_normalize", "name":"normalize", "params":{"bind_model":"normalize_llm"}, + "inputs":{"input":"input.query"}} ``` -输出引用为 `generate_entities.output`。响应在所选输出转换器的 `inputs` 中连接最终结果的来源。 +输出引用为 `normalize.output`。响应在所选输出转换器的 `inputs` 中连接最终结果的来源。 Validator 由引用推导依赖。只有额外执行顺序需要 `depends_on`。必需输入显式连接,可选输入省略即未连接。 内部 Blackboard 数据名就是引用本身,未被引用的输出不发布。换方案时只改引用,逻辑端口保持稳定。 @@ -111,8 +111,8 @@ Spec 包装读取只读输入,必需值缺失或已连接输入类型不符时 | --- | --- | --- | | `type` | `llm` | 模型类别;实现由类别和后端唯一选出 | | `backend.type` | `test_causal_lm_backend` | 运行资源由哪一种后端实现提供;本例为测试后端 | -| `name` | `entity_llm` | Pipeline 为这个模型实例起的名字 | -| 节点配置 `bind_model` | `entity_llm` | 引用上面的模型实例,不是填写模型路径 | +| `name` | `normalize_llm` | Pipeline 为这个模型实例起的名字 | +| 节点配置 `bind_model` | `normalize_llm` | 引用上面的模型实例,不是填写模型路径 | | `ILlmModel` | C++ 接口 | 节点编译时依赖的能力约定 | 模型类型和后端名称只是这个练习的已注册配置,其他环境以 Catalog 为准。 diff --git a/doc/dev_guide/first_control.md b/doc/dev_guide/first_control.md index 3931d2df..42f21a48 100644 --- a/doc/dev_guide/first_control.md +++ b/doc/dev_guide/first_control.md @@ -32,8 +32,9 @@ 发布后不复用。一个命令 ID 只属于一个节点类型,Catalog 会拒绝跨类型重号。 同一节点类型的多个实例共用其命令声明,无需重复注册。 -脚手架生成 `src/custom_nodes/prefix_control_node.cpp`。它与[第一个自定义 Node](first_custom_node.md) -的纯计算节点结构相同,只多了 `Params` 和 Control 命令。该节点按一对一关系处理 `TextBatch`,保留来源编号。脚手架拒绝覆盖已有文件。 +脚手架生成 `src/custom_nodes/prefix_control_node.cpp`。它沿用[第一个自定义 Node](first_custom_node.md) +的参数声明方式,再增加 Control 命令。普通配置只需要 `Params`,运行时更新才需要 Control。 +该节点按一对一关系处理 `TextBatch`,保留来源编号。脚手架拒绝覆盖已有文件。 `--write-test` 同时生成测试文件,节点测试程序自动收集该文件。 ## 3. 阅读受控参数声明 diff --git a/doc/dev_guide/first_custom_node.md b/doc/dev_guide/first_custom_node.md index c2fedd65..769ee8a3 100644 --- a/doc/dev_guide/first_custom_node.md +++ b/doc/dev_guide/first_custom_node.md @@ -1,45 +1,35 @@ -# 第一个自定义 Node:先写逐条处理,再接入模型 +# 第一个自定义 Node:从文本处理逐步加上参数和模型 -所有 Node 使用同一结构。带模型的 Node 只是多声明了模型,并在处理过程中调用它。 -本练习分两部分:先写一个不带模型的文本处理节点,再用同样的结构写一个调用 LLM 的节点。 -两部分都使用现有 Adapter 和测试模型,无需下载权重。 -先按 [README](../../README.md#快速开始)完成构建,所有命令在仓库根目录执行。 +本练习始终修改同一个 `my_text_normalize` 节点:先把“开机检查”改写为“自检”, +再让替换文本可配置,最后调用 LLM 整理整句话。源码文件、节点类型和输入输出端口始终不变。 -如果已有节点能满足需求,直接配置 Pipeline。如需自动生成 LLM 节点的方案和验证命令, -使用[文本 LLM Recipe](recipe_text_llm_node.md)。 - -## 1. 一个 Node 由哪些部分组成 - -| 部分 | 作用 | 是否必需 | +| 阶段 | 要实现的行为 | 在同一份源码上增加什么 | | --- | --- | --- | -| `Inputs` | 声明要读哪些输入 | 必需 | -| `Params` | 声明配置项 | 需要配置时 | -| `Models` | 声明要调用的模型 | 需要模型时 | -| `Run` | 业务逻辑:读取输入,按需调用模型,返回结果 | 必需 | -| `Spec` / `REGISTER_FUNCTION_NODE` | 把上面的声明登记给框架,生成 Definition | 必需 | - -业务逻辑写在 `Run` 中。逻辑复杂时再拆成普通辅助函数,函数名不受框架约束。 -脚手架生成的 `Transform`、`BuildPrompt`、`FormatAnswer` 只是起步写法; -Embedding 等模型节点生成时直接在 `Run` 中调用模型,没有这些函数。 +| 文本处理 | 固定词语替换 | `Inputs`、`Run`、`Spec` | +| 普通配置 | 配置替换后的词语 | `Params` 与字段声明 | +| 调用模型 | 词语替换后,让 LLM 整理表达 | `Models` 与生成调用 | -框架负责模型加载、调度、数据传递和来源编号。外部请求与响应由 Adapter 转换。 +`Run` 写处理流程,`Spec` 把输入、输出和可选的参数、模型登记给框架。 +逻辑较长时可以拆成普通辅助函数;`Transform` 等函数名由你决定。 +逐项处理使用 `MapPayloads`,框架保留数量、顺序和来源编号。 -## 2. 第一部分:不带模型的节点 +先按 [README](../../README.md#快速开始)完成构建,下面的命令都在仓库根目录执行。 +练习使用现有 Adapter 和测试模型,无需下载权重。 +已有节点能满足需求时,直接配置 Pipeline;需要一次生成 LLM 节点及配套方案时,可用 +[文本 LLM Recipe](recipe_text_llm_node.md)。 -本节写一个文本归一化节点:把“开机检查”改写成“自检”,让已有的规则节点能够命中。 +## 1. 先完成文本处理 -### 生成 +只生成一次骨架: ```bash ./tools/scaffold_custom_node.py my_text_normalize --write-test ``` -脚手架生成 `src/custom_nodes/my_text_normalize_node.cpp` 和 -`tests/unit/nodes/test_my_text_normalize_node.cpp`,并拒绝覆盖已有文件。 +得到 `src/custom_nodes/my_text_normalize_node.cpp` 和 +`tests/unit/nodes/test_my_text_normalize_node.cpp`。已有文件会被保留;如果已做过练习,继续编辑它们。 -### 修改业务逻辑 - -脚手架生成的 `Run` 通过 `MapPayloads` 逐条调用辅助函数 `Transform`。在源码中找到 `Transform`,将它换成: +在源码中把 `Transform` 替换为: ```cpp std::string Transform(const std::string& text) { @@ -54,178 +44,229 @@ std::string Transform(const std::string& text) { } ``` -`Transform` 只处理一条文本,`MapPayloads` 保留每条结果的来源编号。 -需要修改输入时,构造新文本,不要修改共享的只读输入。 - -在生成的测试中,把输入 `"example"` 改为 `"请做一次开机检查"`, -把输出断言改为 `"请做一次自检"`。 +其余生成代码保持原样。`Run` 用 `MapPayloads` 对每条文本执行 `Transform`, +`Spec` 声明 `input` 和 `output` 都是文本批次,输入一项就返回一项。 +修改文本时创建新值,不修改共享的只读输入。 -### 编译和测试 +在生成的测试中,将输入 `"example"` 改成 `"请做一次开机检查"`, +将 `item.data` 的期望值改成 `"请做一次自检"`。保留数量与来源编号的断言。 -源码格式化需要 clang-format 18。 +每完成一个阶段,都用下面的命令格式化、编译和运行这个测试: ```bash ./scripts/format.sh -cmake --build build --target alg_sdk alg_pipeline_tool alg_pipeline_tool_test alg_demo edgeflow_test_nodes_runner -j 4 +cmake --build build --target edgeflow_test_nodes_runner alg_pipeline_tool -j 4 ./build/edgeflow_test_nodes_runner --gtest_filter='ScaffoldGeneratedNodeTest.my_text_normalize_*' ./build/alg_pipeline_tool describe-node my_text_normalize ``` -测试应通过。Catalog 应显示 `my_text_normalize`,输入输出都是 `TextBatch`,`model_dependencies` 为空。 +格式化需要 clang-format 18。此时测试应通过,Catalog 的输入输出均为 `TextBatch`, +`model_dependencies` 为空。 -### 接入方案并运行 +## 2. 为同一个节点增加普通配置 -```bash -mkdir -p build/first_node +现在希望不同方案能选择替换后的词语。在 `Inputs` 后添加: + +```cpp +struct Params { + std::string replacement; +}; ``` -保存以下文件为 `build/first_node/pipeline.json`: +将 `Transform` 的签名改为 +`std::string Transform(const std::string& text, const Params& params)`, +把函数体中的 `const std::string to = "自检";` 改成 +`const std::string& to = params.replacement;`。其余替换逻辑保持不变。 -```json -{ - "io": {"input": [{"type":"keyword_in","name":"keyword_match"}], - "output": [{"type":"json_out","name":"keyword_match", - "inputs":{"matches":"matcher.matches"}}]}, - "models": [], - "pipeline": [ - { - "type": "my_text_normalize", - "name": "normalize", - "inputs": {"input": "input.sentence_text"} - }, - { - "type": "text_rule_match", - "name": "matcher", - "params": {"categories": {"SYSTEM_INIT": ["初始化", "自检"]}}, - "inputs": {"text": "normalize.output"} - } - ] +用下面的代码替换原来的 `Run` 和 `Spec`,保留原来的注册语句: + +```cpp +NodeResult Run(const Inputs& inputs, const Params& params) { + return MapPayloads(*inputs.input, [¶ms](const std::string& text) { + return Transform(text, params); + }); +} + +auto Spec() { + return MakeNodeSpec( + InputsOf{Required("input", &Inputs::input)}, + PreservedOutput("output", "input"), + Parameters{Field("replacement", &Params::replacement) + .Default("自检") + .Description("替换开机检查的文本")}, + &Run) + .Description("替换操作说明中的固定词语"); } ``` -`"inputs":{"input":"input.sentence_text"}` 将节点的 `input` 端口连接到输入转换器。 -`matcher` 通过 `normalize.output` 读取归一化结果,Validator 据此推导执行顺序。 +框架从 Pipeline 节点的 `params` 读取 `replacement`,未填写时使用 `自检`。 +这些配置在创建节点时生效。只有需要运行时更新,才添加 [Control](first_control.md)。 -同目录保存 `pipeline.conf`,内容为 `{"pipe_path": "pipeline.json"}`。 -再创建 `input.txt`,写入两行:`请做一次开机检查` 和 `今天天气不错`。 +先运行原来的测试,确认默认值仍然生效。再把测试中 `probe.Init` 的 +`nlohmann::json::object()` 改为 `nlohmann::json{{"replacement", "启动自检"}}`, +将输出期望值改为 `"请做一次启动自检"`,重新运行测试。 +Catalog 现在应显示节点说明和 `replacement`,仍无模型依赖。 -```bash -./build/alg_pipeline_tool validate build/first_node/pipeline.json -./build/alg_pipeline_tool plan build/first_node/pipeline.json -./build/alg_demo --config build/first_node/pipeline.conf --dataset build/first_node/input.txt --output-dir build/first_node/results -``` +## 3. 在同一个节点中调用 LLM -查看 `build/first_node/results/pipeline/results.jsonl`:两条记录的 `status` 都为 0。 -第一条 `is_hit` 为 1,`intent` 为 `SYSTEM_INIT`,`matched_word` 为 `自检`;第二条 `is_hit` 为 0。 -原句只含“开机检查”,命中来自新节点的归一化。 +继续编辑同一个文件。保留 `Inputs`、`Transform` 和注册语句。 +给现有 `Params` 增加生成参数,并在它后面添加 `Models`: + +```cpp +struct Params { + std::string replacement; + GenerateOptions generation; +}; + +struct Models { + LlmCall model; +}; +``` -## 3. 第二部分:同样的结构加一个 LLM +用下面的代码替换 `Run` 和 `Spec`。处理顺序是词语替换、构造提示词、调用模型、清理回答: -本节节点依次处理输入、调用模型、处理输出。生成时加上模型能力: +```cpp +NodeResult Run(const Inputs& inputs, const Params& params, + const Models& models) { + auto prompts = MapPayloads(*inputs.input, [¶ms](const std::string& text) { + return "将下面的操作说明改写为简洁的规范指令,只输出改写结果:\n" + + Transform(text, params); + }); + auto answers = models.model.Generate(prompts, params.generation); + if (!answers.ok()) return answers; + return MapPayloads(answers.value(), [](const std::string& text) { + std::string answer = text; + while (!answer.empty() && answer.back() == '\n') answer.pop_back(); + return answer; + }); +} -```bash -./tools/scaffold_custom_node.py my_business_llm -m llm --write-test +auto Spec() { + return MakeNodeSpec( + InputsOf{Required("input", &Inputs::input)}, + PreservedOutput("output", "input"), + Parameters{Field("replacement", &Params::replacement) + .Default("自检") + .Description("替换开机检查的文本")} + .Include(&Params::generation, GenerateParameters()), + ModelsOf{Model("model", "bind_model", &Models::model)}, &Run) + .Description("替换操作词语,并调用 LLM 整理为规范指令"); +} ``` -对照第一部分的源码,结构相同,多出的只有三处: +`LlmCall` 提供已经准备好的模型能力。`bind_model` 在配置中选择模型名, +`GenerateParameters()` 复用框架的生成参数声明和校验。 +`replacement`、`max_tokens` 等都平铺在节点的 `params` 中,不额外嵌套 `generation`。 +模型失败直接返回,框架不会发布部分结果。 -| 位置 | 变化 | -| --- | --- | -| `Models` | 新增 `LlmCall model;`,Spec 中用 `ModelsOf` 声明,配置里通过 `bind_model` 选择模型 | -| 辅助函数 | `Transform` 换成调用模型前的 `BuildPrompt` 和调用后的 `FormatAnswer` | -| `Run` / Spec | 整批调用一次 `Generate`;`GenerateParameters()` 从配置读取 `max_tokens` 等生成参数 | +### 更新已有测试 -在已有节点上手动加入这些声明,效果与 `-m llm` 生成的结果相同。 +继续修改同一个测试文件。加入头文件 `tests/support/recording_models.h`, +在 `SessionContext session;` 后加入以下模型替身: -### 修改两个辅助函数 +```cpp +auto model = std::make_shared(); +model->replies = {{"请执行启动自检\n"}}; +ASSERT_TRUE(test_support::RegisterNodeModel(session, "fixture", model)); +``` -将 `BuildPrompt` 换成下面的实现,给模型输入加上任务指令: +将 `probe.Init` 的参数对象改为: ```cpp -std::string BuildPrompt(const std::string& text) { - return "你是实体与名词抽取助手。请返回 JSON。\n" + text; -} +nlohmann::json{{"replacement", "启动自检"}, + {"bind_model", "fixture"}, + {"max_tokens", 32}} ``` -将 `FormatAnswer` 换成下面的实现,去掉回答末尾的换行: +输入仍为 `"请做一次开机检查"`,将输出期望值改为 `"请执行启动自检"`, +以确认回答末尾的换行已被清理。在原有结果断言之后补充: ```cpp -std::string FormatAnswer(const std::string& text) { - std::string answer = text; - while (!answer.empty() && answer.back() == '\n') answer.pop_back(); - return answer; -} +ASSERT_EQ(model->calls.size(), 1u); +ASSERT_EQ(model->calls.front().size(), 1u); +EXPECT_EQ(model->calls.front().front().data, + "将下面的操作说明改写为简洁的规范指令,只输出改写结果:\n请做一次启动自检"); +EXPECT_EQ(model->call_options.front().max_tokens, 32); ``` -保留短语 `实体与名词抽取助手`。测试模型据此返回后续节点需要的 JSON。 -该结果用于验证接入流程。提示词效果需要真实模型验证。 +按第一节的命令重新编译和测试。现在测试验证了替换词、实际提示词、生成参数和回答清理; +Catalog 应显示更新后的节点说明、`bind_model`、生成参数和一个 LLM 依赖。 +模型替身只提供确定的回答,业务函数无需识别测试环境。 -生成的测试用 [`RecordingLlm`](../../tests/support/recording_models.h) 作为固定回答的模型。 -把 `model->replies` 中的 `"fixture text"` 改为 `"fixture text\n"`, -输出断言仍保留 `"fixture text"`,用于检查刚添加的末尾换行清理。 -然后按第一部分的命令格式化、编译,并把测试过滤器换成 `my_business_llm_*`。 +## 4. 把这个节点接入 Pipeline -### 复用已有方案和 Adapter - -如果练习文件尚不存在,复制以下配置。已有同名文件时,继续编辑该文件。 +这次只验证文本进、文本出的完整调用,复用现有 `translate` 输入输出转换器: +请求是 `{"query":"..."}`,响应是 `{"translated":"..."}`。 +字段名由已有外部契约决定,节点内部仍只接收和返回文本。 +创建练习目录,并复制现有测试资源: ```bash -cp demo/fixtures/mock/pipeline_entity_extract_custom.json demo/fixtures/mock/pipeline_first_node.json -cp demo/fixtures/mock/pipeline_entity_extract_custom.conf demo/fixtures/mock/pipeline_first_node.conf +mkdir -p build/first_node/artifacts +cp demo/fixtures/mock/artifacts/neutral-llm.fixture build/first_node/artifacts/ ``` -编辑 `pipeline_first_node.json` 中 `name` 为 `generate_entities` 的节点,做两处修改: - -- `type` 改成 `my_business_llm`。 -- 将整个 `params` 对象替换为以下内容。其余生成参数使用默认值。 +保存以下内容为 `build/first_node/pipeline.json`: ```json -{"bind_model": "entity_llm"} +{ + "io": { + "input": [{"type": "entity_in", "name": "translate"}], + "output": [{"type": "json_out", "name": "translate", + "inputs": {"translation": "normalize.output"}}] + }, + "models": [{ + "type": "llm", + "name": "normalize_llm", + "file": "artifacts/neutral-llm.fixture", + "backend": {"type": "test_causal_lm_backend", "params": {"fixed_batch_size": 2}} + }], + "pipeline": [{ + "type": "my_text_normalize", + "name": "normalize", + "params": {"replacement": "启动自检", "bind_model": "normalize_llm", "max_tokens": 32}, + "inputs": {"input": "input.query"} + }] +} ``` -保留 `inputs`、模型、JSON 解析节点和 `io` 连线。 -将 `pipeline_first_node.conf` 的 `pipe_path` 改为 `pipeline_first_node.json`。 +`input.query` 来自输入转换器,`normalize.output` 交给输出转换器。 +Validator 从连线推导依赖。模型的 `file` 相对 Pipeline JSON 所在目录解析。 +这里使用已有测试模型,不增加 Model 或 Backend 实现。 -```mermaid -flowchart LR - A[平台输入文本] --> B[已有 Adapter 解包] - B --> C[my_business_llm: 前处理 → 模型 → 后处理] - C --> D[已有 JSON 解析节点] - D --> E[已有 Adapter 打包实体结果] +同目录保存 `pipeline.conf`,内容为 `{"pipe_path": "pipeline.json"}`。 +再创建 `input.jsonl`,写入两行完整请求: + +```jsonl +{"query":"请做一次开机检查"} +{"query":"设备启动后检查状态"} ``` ```bash -./build/alg_pipeline_tool_test validate demo/fixtures/mock/pipeline_first_node.json -./build/alg_pipeline_tool_test plan demo/fixtures/mock/pipeline_first_node.json -./build/alg_demo --config demo/fixtures/mock/pipeline_first_node.conf --dataset data/corpus_entity_extract.txt --output-dir results/first-node +cmake --build build --target alg_pipeline_tool_test alg_demo -j 4 +./build/alg_pipeline_tool_test validate build/first_node/pipeline.json +./build/alg_pipeline_tool_test plan build/first_node/pipeline.json +./build/alg_demo --config build/first_node/pipeline.conf --dataset build/first_node/input.jsonl --output-dir build/first_node/results ``` -这些配置使用测试模型,校验要用带测试注册的 `alg_pipeline_tool_test`。 -`alg_pipeline_tool` 用于查看生产注册,也能发现刚编译的自定义节点。 - -查看 `results/first-node/pipeline_first_node/results.jsonl`:第一条记录应有 `status: 0`, -以及 `output.nouns: ["mock entity"]`。`summary.json` 应显示一条成功、 -零条失败。回答来自测试模型。 +这些配置使用测试注册,因此校验使用 `alg_pipeline_tool_test`。 +查看 `build/first_node/results/pipeline/results.jsonl`:两条记录都应为 `status: 0`, +`output.translated` 为 `[mock] generated text`;`summary.json` 应显示两条成功、零条失败。 +这是接入验证;前后处理的具体结果由上一节的测试检查。 +替换真实模型时,按目标构建的 Catalog 更新 `models` 与资产路径,再用业务样本验收效果。 -替换真实模型时,按目标构建的 Catalog 更新 `models` 和资产路径,节点的 `bind_model` -引用相应 `name`。再用自己的输入与期望结果验收,不能把测试模型回答作为效果指标。 +## 5. 接下来按需求扩展 -## 4. 从练习进入真实开发 - -在生成的测试中补充自己的业务预期,检查结果与来源,并覆盖空输入、非法输入和模型失败。 -`RecordingLlm` 等替身可通过 `fail_call` 让指定调用失败。参考 -[LLM Node 测试](../../tests/unit/nodes/test_llm_generate_node.cpp)。 +这三个阶段共用一份源码、一条注册语句和相同的端口。是否声明模型由实际算法决定。 +测试跟随业务行为补充;框架初始化和模型替身的使用可查 +[测试说明](../../src/custom_nodes/README.md#本地快速验证)。 交付前执行 `./scripts/run_all_tests.sh`,流程见 [CONTRIBUTING](../../CONTRIBUTING.md)。 -| 接下来遇到的问题 | 去哪里看 | +| 遇到的需求 | 参考 | | --- | --- | -| 端口、编号、模型绑定、Definition、并发是什么意思 | [按需参考](custom_node_concepts.md) | -| 需要配置项或运行时更新 | [Control 练习](first_control.md),生成的节点带一个 `Params` 示例 | -| 其它模型能力 | `-m embedding`、`-m asr`、`-m ocr`、`-m rerank`,结构相同 | -| 需要多个输入或条件重试 | [多输入 starter](../../dev_support/node_authoring/starter_batch_node.cpp) | -| 需要 LLM 与 Embedding 两种能力 | [多模型 starter](../../dev_support/node_authoring/starter_multi_model_node.cpp) | -| 需要配置化模板或复杂后处理 | [复杂算法的组织与现有辅助函数](custom_node_concepts.md#复杂算法仍按普通-c-函数组织) | -| 需要接入全新的平台结构 | [业务接入指南](business_onboarding.md) | -| 需要新增模型语义或硬件后端 | [开发者扩展指南](../developer_guide.md) | - -按操作命名节点,例如“识别实体”或“核对条件”。不同方案可复用同一节点。 +| 更多参数类型、模型能力、端口和来源关系 | [常用写法与按需参考](custom_node_concepts.md) | +| 运行时更新已声明的参数 | [Control 练习](first_control.md) | +| 多输入、条件重试或多模型 | [多输入 starter](../../dev_support/node_authoring/starter_batch_node.cpp)、[多模型 starter](../../dev_support/node_authoring/starter_multi_model_node.cpp) | +| 配置化提示词和复杂后处理 | [复杂算法的组织](custom_node_concepts.md#复杂算法仍按普通-c-函数组织) | +| 新的外部请求或响应契约 | [业务接入指南](business_onboarding.md) | + +节点按操作命名,同一个节点可以在多个方案中复用。 diff --git a/src/custom_nodes/README.md b/src/custom_nodes/README.md index 09cfb02e..ee252607 100644 --- a/src/custom_nodes/README.md +++ b/src/custom_nodes/README.md @@ -16,8 +16,9 @@ ## 通用开发步骤速查 1. 运行 `build/alg_pipeline_tool catalog`,确认已有能力。用 `describe-node` 查看所需节点。 -2. 生成普通函数、Spec 和对应测试。所有节点结构相同;`-m` 只是多声明一个该能力的模型, - `--control-id` 只是多声明参数和 Control 命令: +2. 生成普通函数、Spec 和对应测试。所有节点结构相同;`-m` 多声明一个该能力的模型。 + 普通配置直接添加 `Params` 和字段声明,见[教程第 2 节](../../doc/dev_guide/first_custom_node.md#2-为同一个节点增加普通配置)。 + 需要运行时更新参数时,再用 `--control-id` 生成带 Control 命令的示例: ```bash ./tools/scaffold_custom_node.py custom_filter --write-test