Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"name": "Variable Aggregation",
"uniqueName": ""
},
{
"type": "variableUpdaterNodeState",
"name": "Variable Updater",
"uniqueName": ""
},
{
"type": "fileExtractionNodeState",
"name": "File Extraction",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"name": "变量聚合",
"uniqueName": ""
},
{
"type": "variableUpdaterNodeState",
"name": "变量更新",
"uniqueName": ""
},
{
"type": "fileExtractionNodeState",
"name": "文件提取",
Expand Down
86 changes: 86 additions & 0 deletions app-builder/plugins/aipp-variable-updater/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>modelengine.fit.jade</groupId>
<artifactId>app-builder-plugin-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<groupId>modelengine.fit.jade.plugin</groupId>
<artifactId>aipp-variable-updater</artifactId>

<dependencies>
<dependency>
<groupId>modelengine.fit.jade.waterflow</groupId>
<artifactId>waterflow-runtime-service</artifactId>
</dependency>
<dependency>
<groupId>modelengine.fit.jade</groupId>
<artifactId>aipp-genericable</artifactId>
</dependency>
<dependency>
<groupId>modelengine.fit.jober</groupId>
<artifactId>jober-genericable</artifactId>
</dependency>
<dependency>
<groupId>modelengine.fit.jade</groupId>
<artifactId>aipp-service</artifactId>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.fitframework</groupId>
<artifactId>fit-build-maven-plugin</artifactId>
<version>${fit.version}</version>
<configuration>
<category>user</category>
<level>5</level>
</configuration>
<executions>
<execution>
<id>build-plugin</id>
<goals>
<goal>build-plugin</goal>
</goals>
</execution>
<execution>
<id>package-plugin</id>
<goals>
<goal>package-plugin</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven.antrun.version}</version>
<executions>
<execution>
<phase>install</phase>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}.jar"
todir="../../../build/plugins"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
* This file is a part of the ModelEngine Project.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

package modelengine.fit.jade.aipp.variable.updater;

import modelengine.fit.jober.aipp.common.exception.AippErrCode;
import modelengine.fit.jober.aipp.common.exception.AippParamException;
import modelengine.fit.jober.aipp.constants.AippConst;
import modelengine.fit.jober.common.ErrorCodes;
import modelengine.fit.jober.common.exceptions.JobberException;
import modelengine.fit.waterflow.spi.FlowableService;
import modelengine.fitframework.annotation.Component;
import modelengine.fitframework.annotation.Fitable;
import modelengine.fitframework.util.ObjectUtils;

import java.util.List;
import java.util.Map;

/**
* 变量更新算子服务。
*
* @author 鲁为
* @since 2025-09-26
*/
@Component
public class AippVariableUpdater implements FlowableService {
private static final String UPDATE_VARIABLES = "updateVariables";
private static final String INTERNAL = "_internal";
private static final String OUTPUT_SCOPE = "outputScope";
private static final String KEY = "key";
private static final String VALUE = "value";

@Override
@Fitable("modelengine.fit.jade.aipp.variable.updater")
public List<Map<String, Object>> handleTask(List<Map<String, Object>> flowData) {
Map<String, Object> businessData = this.getBusinessData(flowData);
List<Map<String, Object>> updateVariables = ObjectUtils.cast(businessData.get(UPDATE_VARIABLES));
Map<String, Object> internal = ObjectUtils.cast(businessData.get(INTERNAL));
Map<String, Object> outputScope = ObjectUtils.cast(internal.get(OUTPUT_SCOPE));
for (Map<String, Object> variable : updateVariables) {
List<String> path = ObjectUtils.cast(variable.get(KEY));
Object newValue = variable.get(VALUE);
this.updateNestedMapByPath(outputScope, path, newValue);
}
return flowData;
}

private Map<String, Object> getBusinessData(List<Map<String, Object>> flowData) {
if (flowData.isEmpty() || !flowData.get(0).containsKey(AippConst.BS_DATA_KEY)) {
throw new JobberException(ErrorCodes.INPUT_PARAM_IS_EMPTY, AippConst.BS_DATA_KEY);
}
return ObjectUtils.cast(flowData.get(0).get(AippConst.BS_DATA_KEY));
}

private void updateNestedMapByPath(Map<String, Object> businessData, List<String> path, Object newValue) {
if (businessData == null || path == null || path.isEmpty()) {
return;
}

Map<String, Object> currentMap = businessData;

for (int i = 0; i < path.size() - 1; i++) {
String key = path.get(i);
Object value = currentMap.get(key);

if (value instanceof Map) {
currentMap = ObjectUtils.cast(value);
} else {
throw new AippParamException(AippErrCode.DATA_TYPE_IS_NOT_SUPPORTED);
}
}

String finalKey = path.get(path.size() - 1);
currentMap.put(finalKey, newValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fit:
beans:
packages:
- 'modelengine.fit.jade.aipp.variable.updater'
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
* This file is a part of the ModelEngine Project.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

package modelengine.fit.jade.aipp.variable.updater;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import modelengine.fit.jober.aipp.common.exception.AippParamException;
import modelengine.fitframework.util.MapBuilder;
import modelengine.fitframework.util.ObjectUtils;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* {@link AippVariableUpdater}的测试方法。
*
* @author 鲁为
* @since 2025-09-28
*/
@DisplayName("变量更新节点测试。")
public class AippVariableUpdaterTest {
private static final String UPDATE_VARIABLES = "updateVariables";
private static final String INTERNAL = "_internal";
private static final String OUTPUT_SCOPE = "outputScope";
private static final String KEY = "key";
private static final String VALUE = "value";

private final AippVariableUpdater aippVariableUpdater = new AippVariableUpdater();

@Test
@DisplayName("修改 businessData 中指定路径的值。")
void shouldModifySpecificPathValue() {
Map<String, Object> flowDataInner =
this.getFlowData(this.getContextData(List.of("trace1"), "contextId"), this.buildBusinessData());
List<Map<String, Object>> flowData = List.of(flowDataInner);
this.aippVariableUpdater.handleTask(flowData);
assertEquals(ObjectUtils.<String>cast(ObjectUtils.<Map<String, Object>>cast(ObjectUtils.<Map<String, Object>>cast(
ObjectUtils.<Map<String, Object>>cast(ObjectUtils.<Map<String, Object>>cast(ObjectUtils.<Map<String, Object>>cast(
flowData.get(0).get("businessData")).get(INTERNAL)).get(OUTPUT_SCOPE)).get(
"jade8xvdq4"))
.get("output")).get("a")), "after");
}

@Test
@DisplayName("当 path 不存在时,抛出异常。")
void shouldThrowExceptionWhenPathDoesNotExist() {
Map<String, Object> flowDataInner =
this.getFlowData(this.getContextData(List.of("trace1"), "contextId"), this.buildInvalidBusinessData());
List<Map<String, Object>> flowData = List.of(flowDataInner);
assertThrows(AippParamException.class, () -> this.aippVariableUpdater.handleTask(flowData));
}

private Map<String, Object> getFlowData(Map<String, Object> contextData, Map<String, Object> businessData) {
return MapBuilder.get(() -> new HashMap<String, Object>())
.put("contextData", contextData)
.put("businessData", businessData)
.build();
}

private Map<String, Object> getContextData(List<String> traceIds, String contextId) {
return MapBuilder.get(() -> new HashMap<String, Object>())
.put("flowTraceIds", traceIds)
.put("contextId", contextId)
.build();
}

private Map<String, Object> buildBusinessData() {
Map<Object, Object> output = MapBuilder.get().put("a", "before").build();
Map<Object, Object> node = MapBuilder.get().put("output", output).build();
Map<Object, Object> outputScope = MapBuilder.get().put("jade8xvdq4", node).build();
return MapBuilder.<String, Object>get()
.put(UPDATE_VARIABLES,
List.of(MapBuilder.get()
.put(KEY, List.of("jade8xvdq4", "output", "a"))
.put(VALUE, "after")
.build()))
.put(INTERNAL, MapBuilder.get().put(OUTPUT_SCOPE, outputScope).build())
.build();
}

private Map<String, Object> buildInvalidBusinessData() {
List<Object> output = List.of("a", "before");
Map<Object, Object> node = MapBuilder.get().put("output", output).build();
Map<Object, Object> outputScope = MapBuilder.get().put("jade8xvdq4", node).build();
return MapBuilder.<String, Object>get()
.put(UPDATE_VARIABLES,
List.of(MapBuilder.get()
.put(KEY, List.of("jade8xvdq4", "output", "a"))
.put(VALUE, "after")
.build()))
.put(INTERNAL, MapBuilder.get().put(OUTPUT_SCOPE, outputScope).build())
.build();
}
}
1 change: 1 addition & 0 deletions app-builder/plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<module>aipp-prompt-builder</module>
<module>aipp-rewriter</module>
<module>aipp-variable-aggregation</module>
<module>aipp-variable-updater</module>
<module>aipp-websocket-plugin</module>
<module>app-announcement</module>
<module>app-base</module>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/assets/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const SendIcon = (props) => <Icon component={() => (<BaseIcons.Send />)} {...pro
const ConfigurationIcon = (props) => <Icon component={() => (<BaseIcons.Configuration />)} {...props} />;
const HttpIcon = (props) => <Icon component={() => (<BaseIcons.Http />)} {...props} />;
const VariableAggregation = (props) => <Icon component={() => (<BaseIcons.VariableAggregation />)} {...props} />;
const VariableUpdater = (props) => <Icon component={() => (<BaseIcons.VariableUpdater />)} {...props} />;
const TextToImageIcon = (props) => <Icon component={() => (<BaseIcons.TextToImage />)} {...props} />;
const FileExtractionIcon = (props) => <Icon component={() => (<BaseIcons.FileExtraction />)} {...props} />;
const LoopIcon = (props) => <Icon component={() => (<BaseIcons.Loop />)} {...props} />;
Expand Down Expand Up @@ -118,6 +119,7 @@ export {
ConfigurationIcon,
HttpIcon,
VariableAggregation,
VariableUpdater,
TextToImageIcon,
FileExtractionIcon,
LoopIcon,
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/components/icons/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,23 @@ export const BaseIcons = {
</g>
</svg>
),
VariableUpdater: () => (
<svg width="28.000000" height="28.000000" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<desc>
Created with Pixso.
</desc>
<defs>
<clipPath id="clip10488_209423">
<rect id="转移 (5)" rx="14.000000" width="28.000000" height="28.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<rect id="转移 (5)" rx="14.000000" width="28.000000" height="28.000000" fill="#39C295" fill-opacity="1.000000"/>
<g clip-path="url(#clip10488_209423)">
<path id="减去顶层" d="M19.7134 5L7.32031 5C6.57568 5 6 5.5874 6 6.34717L6 21.6528C6 22.3779 6.57568 23 7.32031 23L14.9995 23C13.7852 22.0879 13 20.6357 13 19C13 16.2388 15.2388 14 18 14C19.1255 14 20.1646 14.3721 21 14.9995L21 6.34717C21 5.5874 20.4243 5 19.7134 5ZM9.75 9.63086C9.8667 9.44922 9.92529 9.20068 9.92529 8.88574L9.92529 8.63867C9.92529 8.38379 9.96924 8.20654 10.0566 8.10742C10.1445 8.0083 10.2915 7.9585 10.4985 7.9585L10.6787 7.9585L10.6787 7L10.271 7C9.93018 7 9.65381 7.03711 9.44141 7.11084C9.33691 7.14697 9.24463 7.19775 9.16504 7.26367L9.16504 7.26367C9.0835 7.33105 9.01514 7.41406 8.95947 7.5127C8.84912 7.70654 8.79395 7.98096 8.79395 8.33496L8.79395 8.72266C8.79395 8.90381 8.77002 9.04492 8.72168 9.14502C8.70801 9.17334 8.69141 9.19922 8.67236 9.22363C8.62451 9.28369 8.56006 9.33105 8.479 9.36475C8.36621 9.41211 8.20703 9.44434 8 9.46143L8 10.5371C8.20215 10.5552 8.35986 10.5864 8.47217 10.6318C8.58545 10.6772 8.66748 10.7495 8.71826 10.8486C8.76855 10.9478 8.79395 11.0903 8.79395 11.2759L8.79395 11.665C8.79395 12.019 8.84912 12.2935 8.95947 12.4873C9.06934 12.6816 9.22998 12.8154 9.44141 12.8892C9.65381 12.9629 9.93018 13 10.271 13L10.6787 13L10.6787 12.04L10.4985 12.04C10.2915 12.04 10.1445 11.9902 10.0566 11.8911C9.96924 11.792 9.92529 11.6147 9.92529 11.3594L9.92529 11.1143C9.92529 10.7993 9.8667 10.5508 9.75 10.3691C9.73438 10.3452 9.71729 10.3218 9.69922 10.2998L9.69922 10.2998C9.58154 10.1558 9.41064 10.0557 9.18701 9.99902C9.44531 9.93506 9.6333 9.81201 9.75 9.63086ZM17.0728 8.63867C17.0728 8.38379 17.0293 8.20654 16.9414 8.10742C16.855 8.0083 16.7075 7.9585 16.5 7.9585L16.3213 7.9585L16.3213 7L16.7275 7C17.0679 7 17.3428 7.03711 17.5518 7.11084C17.7622 7.18359 17.9224 7.31738 18.0322 7.5127C18.1436 7.70654 18.1992 7.98096 18.1992 8.33496L18.1992 8.72266C18.1992 8.90381 18.2231 9.04492 18.2715 9.14502C18.3198 9.24414 18.4014 9.31738 18.5161 9.36475C18.6318 9.41211 18.793 9.44434 19 9.46143L19 10.5371C18.793 10.5552 18.6328 10.5864 18.5195 10.6318C18.4072 10.6772 18.3257 10.7505 18.2749 10.8521C18.2246 10.9531 18.1992 11.0942 18.1992 11.2759L18.1992 11.665C18.1992 12.019 18.1436 12.2935 18.0322 12.4873C17.9224 12.6816 17.7622 12.8154 17.5518 12.8892C17.3428 12.9629 17.0679 13 16.7275 13L16.3213 13L16.3213 12.04L16.5 12.04C16.7075 12.04 16.855 11.9902 16.9414 11.8911C17.0293 11.792 17.0728 11.6147 17.0728 11.3594L17.0728 11.1143C17.0728 10.7993 17.1313 10.5508 17.2485 10.3691C17.3662 10.188 17.5518 10.0645 17.8047 9.99902C17.5518 9.93506 17.3662 9.81201 17.2485 9.63086C17.1313 9.44922 17.0728 9.20068 17.0728 8.88574L17.0728 8.63867ZM14.1797 9.83789L15.9482 7.5376L14.5879 7.5376L13.5308 8.96289L12.4888 7.5376L11.0439 7.5376L12.8125 9.86328L10.9546 12.2803L12.3086 12.2803L13.4551 10.7378L14.5811 12.2803L16.0376 12.2803L14.1797 9.83789Z" clip-rule="evenodd" fill="#FFFFFF" fill-opacity="0.800000" fill-rule="evenodd"/>
<path id="减去顶层" d="M18 15C15.791 15 14 16.791 14 19C14 21.209 15.791 23 18 23C20.209 23 22 21.209 22 19C22 16.791 20.209 15 18 15ZM18.707 19.7676C18.5088 19.9658 18.5088 20.2769 18.707 20.4746C18.9048 20.6729 19.2158 20.6729 19.4141 20.4746L20.4746 19.4141C20.5234 19.3652 20.5601 19.3115 20.5845 19.2524C20.6089 19.1934 20.6211 19.1294 20.6211 19.0605C20.6211 18.9224 20.5723 18.8047 20.4746 18.707L19.4141 17.6465C19.2158 17.4482 18.9048 17.4482 18.707 17.6465C18.5088 17.8442 18.5088 18.1553 18.707 18.3535L18.8535 18.5L16 18.5L16 18C16 17.7202 15.7798 17.5 15.5 17.5C15.2197 17.5 15 17.7202 15 18L15 19C15 19.0693 15.0122 19.1333 15.0366 19.1919C15.061 19.251 15.0977 19.3047 15.1465 19.3535C15.1953 19.4023 15.249 19.439 15.3081 19.4634C15.3667 19.4878 15.4312 19.5 15.5 19.5L18.9746 19.5L18.707 19.7676Z" clip-rule="evenodd" fill="#FFFFFF" fill-opacity="1.000000" fill-rule="evenodd"/>
</g>
</svg>
),
TextToImage: () => (
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none">
<defs>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/addFlow/components/basic-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TextExtractionIcon,
HttpIcon,
VariableAggregation,
VariableUpdater,
TextToImageIcon,
FileExtractionIcon,
LoopIcon,
Expand Down Expand Up @@ -55,6 +56,7 @@ const BasicItems = (props: any) => {
'questionClassificationNodeCondition': <ClassificationIcon />,
'httpNodeState': <HttpIcon />,
'variableAggregationNodeState': <VariableAggregation />,
'variableUpdaterNodeState': <VariableUpdater />,
'textToImageNodeState': <TextToImageIcon />,
'fileExtractionNodeState': <FileExtractionIcon />,
'noteNode': <ClassificationIcon />,
Expand Down