Skip to content
Merged
4 changes: 2 additions & 2 deletions app-builder/plugins/aipp-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@
<artifactId>waterflow-graph-service</artifactId>
</dependency>
<dependency>
<groupId>org.fitframework.fel</groupId>
<artifactId>tool-mcp-client-service</artifactId>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
</dependency>

<!-- Redis -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import modelengine.fel.core.chat.ChatModel;
import modelengine.fel.core.chat.Prompt;
import modelengine.fel.engine.operators.patterns.AbstractAgent;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fel.tool.service.ToolExecuteService;
import modelengine.fit.jober.aipp.constants.AippConst;
import modelengine.fit.jober.aipp.util.McpClientFactory;
import modelengine.fitframework.annotation.Bean;
import modelengine.fitframework.annotation.Component;
import modelengine.fitframework.annotation.Fit;
Expand All @@ -29,12 +29,13 @@ public class FelComponentConfig {
*
* @param toolExecuteService 表示工具调用服务的 {@link ToolExecuteService}。
* @param chatModel 表示模型流式服务的 {@link ChatModel}。
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
* @return 返回 WaterFlow 场景的 Agent 服务的 {@link AbstractAgent}{@code <}{@link Prompt}{@code ,
* }{@link Prompt}{@code >}。
*/
@Bean(AippConst.WATER_FLOW_AGENT_BEAN)
public AbstractAgent getWaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatModel, McpClientFactory mcpClientFactory) {
public AbstractAgent getWaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatModel,
McpClientFactory mcpClientFactory) {
return new WaterFlowAgent(toolExecuteService, chatModel, mcpClientFactory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

package modelengine.fit.jober.aipp.fel;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import modelengine.fel.core.chat.ChatMessage;
import modelengine.fel.core.chat.ChatModel;
import modelengine.fel.core.chat.Prompt;
Expand All @@ -22,20 +19,20 @@
import modelengine.fel.engine.operators.models.ChatChunk;
import modelengine.fel.engine.operators.models.ChatFlowModel;
import modelengine.fel.engine.operators.patterns.AbstractAgent;
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fel.tool.service.ToolExecuteService;
import modelengine.fit.jober.aipp.common.exception.AippErrCode;
import modelengine.fit.jober.aipp.common.exception.AippException;
import modelengine.fit.jober.aipp.constants.AippConst;
import modelengine.fit.jober.aipp.util.LangChain4jMcpClient;
import modelengine.fit.jober.aipp.util.McpClientFactory;
import modelengine.fit.jober.aipp.util.McpUtils;
import modelengine.fit.waterflow.domain.context.StateContext;
import modelengine.fitframework.annotation.Fit;
import modelengine.fitframework.inspection.Validation;
import modelengine.fitframework.log.Logger;
import modelengine.fitframework.util.CollectionUtils;
import modelengine.fitframework.util.ObjectUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -61,14 +58,14 @@ public class WaterFlowAgent extends AbstractAgent {
*
* @param toolExecuteService 表示工具调用服务的 {@link ToolExecuteService}。
* @param chatStreamModel 表示流式对话大模型的 {@link ChatModel}。
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
*/
public WaterFlowAgent(@Fit ToolExecuteService toolExecuteService, ChatModel chatStreamModel,
McpClientFactory mcpClientFactory) {
super(new ChatFlowModel(chatStreamModel, null));
this.toolExecuteService = Validation.notNull(toolExecuteService, "The tool execute service cannot be null.");
this.mcpClientFactory = Validation.notNull(mcpClientFactory, "The mcp client factory cannot be null.");
this.agentMsgKey = AGENT_MSG_KEY;
this.mcpClientFactory = mcpClientFactory;
}

@Override
Expand Down Expand Up @@ -136,12 +133,10 @@ private ChatMessage callTool(ToolCall toolCall, Map<String, ToolInfo> toolsMap,
if (mcpServerConfig != null) {
String url = Validation.notBlank(ObjectUtils.cast(mcpServerConfig.get(AippConst.MCP_SERVER_URL_KEY)),
"The mcp url should not be empty.");
try (McpClient mcpClient = this.mcpClientFactory.create(McpUtils.getBaseUrl(url),
McpUtils.getSseEndpoint(url))) {
mcpClient.initialize();
Object result = mcpClient.callTool(toolRealName, JSONObject.parseObject(toolCall.arguments()));
return new ToolMessage(toolCall.id(), JSON.toJSONString(result));
} catch (IOException exception) {
try (LangChain4jMcpClient mcpClient = this.mcpClientFactory.create(url)) {
String result = mcpClient.callTool(toolRealName, toolCall.arguments());
return new ToolMessage(toolCall.id(), result);
} catch (Exception exception) {
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED, exception.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
import modelengine.fel.engine.flows.AiProcessFlow;
import modelengine.fel.engine.operators.patterns.AbstractAgent;
import modelengine.fel.engine.operators.prompts.Prompts;
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fel.tool.mcp.entity.Tool;
import modelengine.fel.tool.model.transfer.ToolData;
import modelengine.fit.jober.aipp.domains.appversion.service.AppVersionService;
import modelengine.fit.jober.aipp.enums.MetaInstStatusEnum;
import modelengine.fit.jober.aipp.util.McpUtils;
import modelengine.fit.jober.aipp.util.LangChain4jMcpClient;
import modelengine.fit.jober.aipp.util.McpClientFactory;
import modelengine.fitframework.inspection.Validation;
import modelengine.jade.store.service.ToolService;
import modelengine.fit.jade.aipp.formatter.OutputFormatterChain;
Expand Down Expand Up @@ -70,6 +68,9 @@
import modelengine.fitframework.util.StringUtils;
import modelengine.fitframework.util.UuidUtils;

import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.agent.tool.ToolSpecification;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -113,9 +114,9 @@ public class LlmComponent implements FlowableService {
private final AippModelCenter aippModelCenter;
private final PromptBuilderChain promptBuilderChain;
private final AppTaskInstanceService appTaskInstanceService;
private final McpClientFactory mcpClientFactory;
private final OutputFormatterChain formatterChain;
private final AppVersionService appVersionService;
private final McpClientFactory mcpClientFactory;

/**
* 大模型节点构造器,内部通过提供的 agent 和 tool 构建智能体工作流。
Expand All @@ -129,7 +130,9 @@ public class LlmComponent implements FlowableService {
* @param aippModelCenter 表示模型中心的 {@link AippModelCenter}。
* @param promptBuilderChain 表示提示器构造器职责链的 {@link PromptBuilderChain}。
* @param appTaskInstanceService 表示任务实例服务的 {@link AppTaskInstanceService}。
* @param mcpClientFactory 表示大模型上下文客户端工厂的 {@link McpClientFactory}。
* @param formatterChain 表示输出格式化器链的 {@link OutputFormatterChain}。
* @param appVersionService 表示应用版本服务的 {@link AppVersionService}。
* @param mcpClientFactory 表示 MCP 客户端工厂的 {@link McpClientFactory}。
*/
public LlmComponent(FlowInstanceService flowInstanceService,
@Fit ToolService toolService,
Expand All @@ -141,13 +144,15 @@ public LlmComponent(FlowInstanceService flowInstanceService,
PromptBuilderChain promptBuilderChain,
AppTaskInstanceService appTaskInstanceService,
OutputFormatterChain formatterChain,
McpClientFactory mcpClientFactory, AppVersionService appVersionService) {
AppVersionService appVersionService,
McpClientFactory mcpClientFactory) {
this.flowInstanceService = flowInstanceService;
this.toolService = toolService;
this.aippLogService = aippLogService;
this.aippLogStreamService = aippLogStreamService;
this.serializer = notNull(serializer, "The serializer cannot be nul.");
this.aippModelCenter = aippModelCenter;
this.mcpClientFactory = mcpClientFactory;

// handleTask从入口开始处理,callback从agent node开始处理
this.agentFlow = AiFlows.<Tip>create()
Expand All @@ -157,7 +162,6 @@ public LlmComponent(FlowInstanceService flowInstanceService,
.close();
this.promptBuilderChain = promptBuilderChain;
this.appTaskInstanceService = appTaskInstanceService;
this.mcpClientFactory = notNull(mcpClientFactory, "The mcp client factory cannot be null.");
this.formatterChain = formatterChain;
this.appVersionService = appVersionService;
}
Expand Down Expand Up @@ -482,12 +486,12 @@ private List<ToolInfo> buildMcpToolInfos(Map<String, Object> mcpServersConfig) {
String url = Validation.notBlank(ObjectUtils.cast(serverConfig.get(AippConst.MCP_SERVER_URL_KEY)),
"The mcp url should not be empty.");

try (McpClient mcpClient = this.mcpClientFactory.create(McpUtils.getBaseUrl(url),
McpUtils.getSseEndpoint(url))) {
mcpClient.initialize();
List<Tool> tools = mcpClient.getTools();
result.addAll(tools.stream().map(tool -> buildMcpToolInfo(serverName, tool, serverConfig)).toList());
} catch (IOException exception) {
try (LangChain4jMcpClient mcpClient = this.mcpClientFactory.create(url)) {
List<ToolSpecification> tools = mcpClient.getTools();
result.addAll(tools.stream()
.map(tool -> buildMcpToolInfo(serverName, tool, serverConfig))
.toList());
} catch (Exception exception) {
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED, exception.getMessage());
}
});
Expand Down Expand Up @@ -515,14 +519,23 @@ private ToolInfo buildToolInfo(ToolData toolData) {
.build();
}

private static ToolInfo buildMcpToolInfo(String serverName, Tool tool, Map<String, Object> serverConfig) {
private static ToolInfo buildMcpToolInfo(String serverName,
ToolSpecification tool, Map<String, Object> serverConfig) {
JsonObjectSchema toolParams = tool.parameters();
Map<String, Object> parametersMap = new HashMap<>();
if (toolParams != null) {
parametersMap.put("type", "object");
parametersMap.put("properties", toolParams.properties());
parametersMap.put("required", toolParams.required());
}

return ToolInfo.custom()
.name(buildUniqueToolName(AippConst.MCP_SERVER_TYPE, serverName, tool.getName()))
.description(tool.getDescription())
.parameters(tool.getInputSchema())
.name(buildUniqueToolName(AippConst.MCP_SERVER_TYPE, serverName, tool.name()))
.description(tool.description())
.parameters(parametersMap)
.extensions(MapBuilder.<String, Object>get()
.put(AippConst.MCP_SERVER_KEY, serverConfig)
.put(AippConst.TOOL_REAL_NAME, tool.getName())
.put(AippConst.TOOL_REAL_NAME, tool.name())
.build())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* 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.jober.aipp.util;

import modelengine.fitframework.annotation.Component;

/**
* MCP 客户端工厂的默认实现,使用 {@link LangChain4jMcpClient} 创建客户端实例。
*
* @author songyongtan
* @since 2026-03-02
*/
@Component
public class DefaultMcpClientFactory implements McpClientFactory {
@Override
public LangChain4jMcpClient create(String url) {
return new LangChain4jMcpClient(url);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* 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.jober.aipp.util;

import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;

import modelengine.fit.jober.aipp.common.exception.AippErrCode;
import modelengine.fit.jober.aipp.common.exception.AippException;
import modelengine.fitframework.util.StringUtils;

import java.util.List;

/**
* LangChain4jMcpClient is a client for calling ModelEngine's MCP server.
*
* @author songyongtan
* @since 2026-03-01
*/
public class LangChain4jMcpClient implements AutoCloseable {
private final McpClient mcpClient;
private final String url;

/**
* 构造函数,用于初始化LangChain4jMcpClient对象。
*
* @param url MCP服务器的URL,格式为http://host:port
*/
public LangChain4jMcpClient(String url) {
this.url = url;

HttpMcpTransport transport = new HttpMcpTransport.Builder()
.sseUrl(url)
.build();

this.mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
}

/**
* 获取MCP服务器上注册的所有工具。
*
* @return 包含所有工具规范的列表
*/
public List<ToolSpecification> getTools() {
try {
return this.mcpClient.listTools();
} catch (Exception e) {
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
StringUtils.format("Failed to get tools from MCP server. [url={0}]", this.url), e);
}
}

/**
* 调用MCP服务器上的指定工具。
*
* @param toolName 要调用的工具名称
* @param arguments 工具调用的参数,格式为JSON字符串
* @return 工具调用的结果,格式为JSON字符串
*/
public String callTool(String toolName, String arguments) {
try {
ToolExecutionRequest request = ToolExecutionRequest.builder()
.name(toolName)
.arguments(arguments)
.build();
return mcpClient.executeTool(request).resultText();
} catch (Exception e) {
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
StringUtils.format("Failed to call tool. [toolName={0}, url={1}]", toolName, this.url), e);
}
}

@Override
public void close() {
try {
this.mcpClient.close();
} catch (Exception e) {
throw new AippException(AippErrCode.CALL_MCP_SERVER_FAILED,
StringUtils.format("Failed to close MCP client. [url={0}]", this.url), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* 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.jober.aipp.util;

/**
* MCP 客户端工厂接口,用于创建 {@link LangChain4jMcpClient} 实例。
*
* @author songyongtan
* @since 2026-03-02
*/
public interface McpClientFactory {
/**
* 创建 MCP 客户端实例。
*
* @param url 表示 MCP 服务器的 URL。
* @return 返回创建的 {@link LangChain4jMcpClient} 实例。
*/
LangChain4jMcpClient create(String url);
}
Loading
Loading