这篇练习面向会写 C++、希望实现“输入处理 → 调用模型 → 输出处理”的方案开发者。 完成后,你会得到一个能通过统一 Demo 运行的节点,并知道下一次应该修改哪些文件。
先确认是否需要写代码:已有节点能通过连线或配置完成的需求,直接用 Pipeline。 下面用实体抽取指令做教学练习;这个任务本身也能用已有通用节点完成。
本练习使用既有模型能力和实体输入输出结构,只修改:
| 文件 | 你要做什么 |
|---|---|
src/custom_nodes/my_business_llm_node.cpp |
脚手架生成后,填写两个文本函数 |
src/custom_nodes/CMakeLists.txt |
脚手架自动登记源码 |
demo/fixtures/mock/pipeline_first_node.json |
在复制的方案中选用新节点 |
demo/fixtures/mock/pipeline_first_node.conf |
指向新方案,复用已有模型路径和输出容量 |
调度、模型加载和平台数据拷贝继续由框架承担。Node 返回内部文本,Adapter 在方案执行 完成后将最终值转换到平台结构。你不用在 Node 中操作平台指针或输出池。
基础模板源码由两个文本函数、Spec 声明
和注册宏组成,默认做文本透传和模型调用。--authoring basic 直接使用这份文件,
生成的源码会进入现有测试 runner 编译。
先关注 BuildPrompt 和 FormatAnswer 两个普通函数;其余部分可结合
五个概念说明逐步阅读。
以下命令都从仓库根目录执行。前提是已经按根目录 README 完成快速开始构建;本练习使用确定性测试模型,不需要下载真实模型。
先查询当前构建,确认要复用的业务和节点:
./build/alg_pipeline_tool catalog --biz entity_extract_v1
./build/alg_pipeline_tool describe-node StructuredJsonParseNode再生成源码:
./scripts/scaffold_custom_node.py MyBusinessLlmNode --kind model -m llm --authoring basic --add-to-cmake --write-test打开 src/custom_nodes/my_business_llm_node.cpp。文件中的主要内容分成三部分:
| 位置 | 第一次开发时怎么处理 |
|---|---|
顶部 BuildPrompt / FormatAnswer |
填写业务逻辑:接收一个字符串,返回一个字符串 |
MakeLlmTextSpec |
声明输入输出,组合两个文本函数与一次 LLM 调用 |
REGISTER_FUNCTION_NODE |
从同一 Spec 注册构造方法和 Definition |
两个文本函数是源文件内的普通函数,不需要继承节点类。你可以根据业务继续拆分小函数。
源文件按操作放在 custom_nodes,另一个方案复用时直接引用同一个节点类型。
在 BuildPrompt 中,将默认的 return text; 换成下面这行,给模型输入加上任务指令:
return "实体抽取:\n" + text;在 FormatAnswer 中,将默认的 return text; 换成下面几行,去掉回答末尾的换行:
std::string answer = text;
while (!answer.empty() && answer.back() == '\n') answer.pop_back();
return answer;你处理的是 text;固定结构负责把处理结果放回原来的 (req_id, sub_id),不会把
第二条回答当成第一条的结果。前处理从只读输入构造新文本,不修改其他节点共享的输入。
这里没有模板语言、参数解析或 Markdown 解析器。需要这些能力时再使用已有通用节点,
或者参考完整样例中对应的一小部分。需要多输入或条件二次推理时,参考
自由 Batch 示例;需要两种模型能力时,参考
多模型示例。它们用普通 Run 函数、
InputsOf、Parameters 和 ModelsOf 声明输入、参数及模型槽位。输出数量变化、Control 等
尚未被基础包装覆盖的需求,继续使用高级生命周期模板。
外部 C ABI 请求的字段选择与响应组装属于 Adapter,不能移到 Node 或 Demo;见
输入输出边界。
修改 C++ 后需安装 clang-format 18,并确保 clang-format 命令指向该版本;
下面的格式脚本会检查版本。这是源码开发步骤,单纯配置方案不需要它。
./scripts/format.sh
cmake --build build --target alg_sdk alg_pipeline_tool alg_pipeline_tool_test alg_demo -j 4
./build/alg_pipeline_tool describe-node MyBusinessLlmNode看到 node_type 为 MyBusinessLlmNode、输入输出为 TextBatch,说明构建和注册已完成。
仅创建 .cpp 文件还不够;--add-to-cmake 负责把它加入构建,重新编译才会进入 Catalog。
复制这两份已存在的配置,避免从空白文件猜业务输入输出: 如果已有同名练习文件,继续编辑它们即可,不必重新复制。
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编辑 pipeline_first_node.json 中 id 为 custom_prompt 的节点,做两处修改:
node_type改成MyBusinessLlmNode。- 将整个
config对象替换为下面的内容。轻量节点只声明了模型绑定,不接受完整样例的 模板、清洗和采样配置字段。
{"bind_model": "entity_llm"}保留该节点的 ports 和 depends_on,以及其他模型与 JSON 解析节点。
在 pipeline_first_node.conf 中,将 pipe_path 改为
pipeline_first_node.json(.conf 仅包含该定位字段;接入绑定与输出容量沿用 JSON 中的 deployment)。
此时数据经过:
flowchart LR
A[平台输入文本] --> B[已有 Adapter 解包]
B --> C[MyBusinessLlmNode: 前处理 → 模型 → 后处理]
C --> D[已有 JSON 解析节点]
D --> E[已有 Adapter 打包实体结果]
为什么节点里叫 input,方案里却叫 input_sentences?前者是这个操作的接口名,后者是
当前方案给数据取的名字;ports 将它们连起来。下一个方案可以换数据名,节点代码继续复用。
./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 --biz entity_extract --config demo/fixtures/mock/pipeline_first_node.conf --dataset data/corpus_entity_extract.txt --output-dir results/first-node这些配置使用测试模型,校验要用带测试注册的 alg_pipeline_tool_test。
alg_pipeline_tool 用于查看生产注册,也能发现刚编译的自定义节点。
查看 results/first-node/entity_extract/results.jsonl:应有 request_id: 30001、status: 0,
以及 output.entities.nouns 中的“张三”“清华大学”等值。summary.json 应显示一条成功、
零条失败。这里验证节点、编排与 Adapter 的完整路径,回答来自确定性测试模型。
替换真实模型时,按目标构建的 Catalog 更新 models 和资产路径,节点的 bind_model
引用相应 model_id。再用自己的输入与期望结果验收,不能把测试模型回答作为效果指标。
首次运行只需要两个业务函数。准备交付时,把业务断言加入现有节点测试;可参考
test_common_nodes.cpp 中的
StarterTextFunctionsFollowTheDocumentedExercise。该测试编译本文的两个函数体,检查模型
实际收到的提示词、后处理结果、多条输入的来源,以及输入快照未被修改。
本练习的 --write-test 会创建并登记真实测试文件,使用 NodeHarness 注入输入与 mock,
检查输出及模型调用。修改算法后同步填写独立业务期望;--generate-test 只打印注册片段。已有测试覆盖模型失败和错误来源时不发布输出;你的算法
还应覆盖自己的边界输入。交付执行 ./scripts/run_all_tests.sh,流程见
CONTRIBUTING。
| 接下来遇到的问题 | 去哪里看 |
|---|---|
| 端口、编号、模型绑定、Definition、并发是什么意思 | 五个概念说明 |
| 需要多个输入或条件重试 | 自由 Batch starter |
| 需要 LLM 与 Embedding 两种能力 | 多模型 starter |
| 需要配置化模板或复杂后处理 | 复杂算法的组织与现有辅助函数 |
| 需要接入全新的平台结构 | 业务接入指南 |
| 需要新增模型语义或硬件后端 | 开发者扩展指南 |
练习生成的节点与配置就是普通源码扩展。真正接入时用操作语义命名,例如“识别实体”或 “核对条件”,避免把项目名固化进节点或为每个业务再建目录。