Skip to content

Commit

Permalink
feat(agent-plan): team builder (#912)
Browse files Browse the repository at this point in the history
* feat(agent-plan): team builder

* feat(multiagent): team builder - 1

* feat(multiagent): team builder - 2

* feat(tool):add tool registry

* feat(m78): add m78 client

---------

Co-authored-by: wangyingjie3 <wangyingjie3@xiaomi.com>
HawickMason and wangyingjie3 authored Jan 13, 2025
1 parent 27593d6 commit aef6761
Showing 38 changed files with 2,577 additions and 2 deletions.
1 change: 1 addition & 0 deletions jcommon/.gitignore
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ dependency-reduced-pom.xml
*.diff
.DS_Store
.idea/
.vscode/
*.iml
logs/
.project
95 changes: 95 additions & 0 deletions jcommon/ai/m78/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?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>run.mone</groupId>
<artifactId>ai</artifactId>
<version>1.4-jdk20-SNAPSHOT</version>
</parent>

<artifactId>m78</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
<optional>true</optional>
</dependency>
<!-- FIXME: 讲道理client里不应该引入lombok,先这样吧... -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.0-jre</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.0</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<optional>true</optional>
<version>1.2.9</version>
</dependency>


<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerVersion>8</compilerVersion>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
16 changes: 16 additions & 0 deletions jcommon/ai/m78/src/main/java/run.mone.m78.client/M78Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package run.mone.m78.client;

import run.mone.m78.client.model.ClientType;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:36
*/
public interface M78Client {

String getName();

ClientType getClientType();


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package run.mone.m78.client;

import run.mone.m78.client.bot.BotHttpClient;
import run.mone.m78.client.bot.BotWsClient;
import run.mone.m78.client.flow.FlowHttpClient;
import run.mone.m78.client.flow.FlowWsClient;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:46
*/
public class M78ClientBuilder {

private String m78BotHttpEndpoint = "";

private String m78BotWsEndpoint = "";

private String m78FlowHttpEndpoint = "";

private String m78FlowWsEndpoint = "";

public BotHttpClient buildBotHttpClient() {
// TODO
return null;
}


public BotWsClient buildBotWsClient() {
// TODO
return null;
}

public FlowHttpClient buildFlowHttpClient() {
// TODO
return null;
}

public FlowWsClient buildFlowWsClient() {
// TODO
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package run.mone.m78.client.bot;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import run.mone.m78.client.model.ClientType;
import run.mone.m78.client.M78Client;
import run.mone.m78.client.model.History;
import run.mone.m78.client.model.M78BotReq;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
* <p>BotHttpClient类实现了M78Client接口,负责与M78Bot接口进行HTTP通信。
* 该类提供了构建HTTP请求、发送请求并处理响应的功能。
* <blockquote><pre>
* 主要功能包括:
* - 通过callBot方法调用M78Bot接口并返回响应结果。
* - 提供获取客户端名称和类型的方法。
* - 提供一个内部Builder类用于构建BotHttpClient实例。
* </pre></blockquote>
* 该类使用了OkHttpClient进行HTTP请求,并支持通过JsonObject传递额外的参数信息。
* 需要传递token进行授权。
*
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:36
* @see M78Client
* @see ClientType
* @see M78BotReq
* @see JsonObject
* @see OkHttpClient
*/
@Slf4j
public class BotHttpClient implements M78Client {

private String name = ClientType.BOT_HTTP.getTypeName();

private String url = "https://mone.test.mi.com/open-apis/ai-plugin-new/feature/router/probot/query";

private String token;

private String model;

private final ClientType type = ClientType.BOT_HTTP;

private long timeout = 60; // second

public BotHttpClient() {
// default constructor
}

private BotHttpClient(Builder builder) {
name = builder.name;
url = builder.url;
token = builder.token;
timeout = builder.timeout;
}


public String callBot(M78BotReq botReq, JsonObject jsonObject) {
return callBot(botReq, jsonObject, null);
}


public static Function<String, String> DEFAULT_FUNCTION = res -> {
log.info("res:{}", res);
JsonObject resObj = JsonParser.parseString(JsonParser.parseString(res)
.getAsJsonObject()
.get("data").getAsString())
.getAsJsonObject()
.get("result")
.getAsJsonObject();
JsonElement data = null;
if (resObj.has("data")) {
data = resObj.get("data");
} else {
data = resObj.get("content");
}
if (data.isJsonPrimitive()) {
return data.getAsString();
}
return data.toString();
};

/**
* 调用M78Bot接口并返回响应结果
*
* @param botReq 包含请求信息的M78BotReq对象
* @param jsonObject 额外的参数信息,JsonObject类型
* @return 接口响应结果字符串,若发生异常则返回空字符串
*/
public String callBot(M78BotReq botReq, JsonObject jsonObject, Function<String, String> function) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.build();

// 使用 Gson 的 JsonObject 构建请求体
JsonObject mainObject = new JsonObject();
mainObject.addProperty("userName", botReq.getUserName());
mainObject.addProperty("botId", botReq.getBotId());
mainObject.addProperty("input", botReq.getInput());

if (StringUtils.isNotBlank(this.model)) {
mainObject.addProperty("model", this.model);
}

if (StringUtils.isNotEmpty(botReq.getToken())) {
mainObject.addProperty("token", botReq.getToken());
}

if (jsonObject != null) {
if (jsonObject.has("history")) {
JsonElement history = jsonObject.remove("history");
mainObject.add("history", history);
}
if (jsonObject.size() > 0) {
mainObject.add("params", jsonObject);
}
}

String jsonBody = mainObject.toString();

RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonBody);

log.debug("BotHttpClient callBot jsonBody:{}", jsonBody);

Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Accept", "application/json, text/plain, */*")
.addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.addHeader("Cache-Control", "no-cache")
.addHeader("Connection", "keep-alive")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", Optional.ofNullable(token).orElseThrow(() -> new IllegalArgumentException("须传递token!")))
.build();

try {
Response response = client.newCall(request).execute();
String res = response.body().string();
if (null == function) {
return res;
}
return function.apply(res);
} catch (Throwable e) {
log.error("callBot error", e);
}
return "";
}


public String callBot(M78BotReq botReq, JsonObject jsonObject, History history, Function<String, String> function) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.callTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.build();

// 使用 Gson 的 JsonObject 构建请求体
JsonObject mainObject = new JsonObject();
mainObject.addProperty("userName", botReq.getUserName());
mainObject.addProperty("botId", botReq.getBotId());
mainObject.addProperty("input", botReq.getInput());

if (null != history) {
mainObject.add("history", new Gson().toJsonTree(history.getMessages()));
}

if (jsonObject != null) {
if (jsonObject.size() > 0) {
mainObject.add("params", jsonObject);
}
}

String jsonBody = mainObject.toString();

RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonBody);

Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Accept", "application/json, text/plain, */*")
.addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.addHeader("Cache-Control", "no-cache")
.addHeader("Connection", "keep-alive")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", Optional.ofNullable(token).orElseThrow(() -> new IllegalArgumentException("须传递token!")))
.build();

try {
Response response = client.newCall(request).execute();
String res = response.body().string();
if (null == function) {
return res;
}
return function.apply(res);
} catch (Throwable e) {
log.error("callBot error", e);
}
return "";
}


@Override
public String getName() {
return StringUtils.isNotBlank(name) ? name : type.getTypeName();
}

@Override
public ClientType getClientType() {
return type;
}

public static BotHttpClient.Builder builder() {
return new BotHttpClient.Builder();
}


public static final class Builder {
private String name = ClientType.BOT_HTTP.getTypeName();
private String url = "https://mone.test.mi.com/open-apis/ai-plugin-new/feature/router/probot/query";
private String token;

private String model;

private long timeout = 60; // second

public Builder() {
}

public Builder name(String val) {
name = val;
return this;
}

public Builder url(String val) {
url = val;
return this;
}

public Builder token(String val) {
token = val;
return this;
}

public Builder model(String val) {
model = val;
return this;
}

public Builder timeout(long val) {
timeout = val;
return this;
}

public BotHttpClient build() {
return new BotHttpClient(this);
}
}
}
256 changes: 256 additions & 0 deletions jcommon/ai/m78/src/main/java/run.mone.m78.client/bot/BotWsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package run.mone.m78.client.bot;

import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import org.apache.commons.lang3.StringUtils;
import run.mone.m78.client.M78Client;
import run.mone.m78.client.model.M78Message;
import run.mone.m78.client.model.M78MessageCategory;
import run.mone.m78.client.model.M78MessageType;
import run.mone.m78.client.model.ClientType;
import run.mone.m78.client.util.Base64Utils;
import run.mone.m78.client.util.GsonUtils;

import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:32
*/
@Slf4j
public class BotWsClient implements M78Client {

private String name;

private final ClientType type = ClientType.BOT_WS;
private WebSocket ws;

private String projectName;

private String url = "ws://10.38.204.190:8076/ws/bot/abc";

private String token = "";

private CountDownLatch latch;

private BotWsClient(Builder builder) {
name = builder.name;
projectName = builder.projectName;
url = builder.url;
token = builder.token;
latch = builder.latch;
}

public void start(Consumer<M78Message> consumer) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.header("athena-token", token)
.build();

StringBuilder sb = new StringBuilder();

WebSocketListener listener = new WebSocketListener() {

Stopwatch sw = Stopwatch.createStarted();

private AtomicBoolean cancel = new AtomicBoolean(false);


@Override
public void onOpen(WebSocket webSocket, Response response) {
log.info("ws open");
}


@Override
public void onMessage(WebSocket webSocket, String text) {
log.info("Received:{}", text);
JsonObject msg = JsonParser.parseString(text).getAsJsonObject();
String type = GsonUtils.get(msg, "type", "");
String messageType = GsonUtils.get(msg, "messageType", "");

if (StringUtils.isBlank(messageType)) {
log.warn("no valid message type, will discard!");
return;
}
if (messageType.startsWith("BOT")) {
dispatchBotMsg(webSocket, messageType, msg);
} else if (messageType.startsWith("FLOW")) {
dispatchFlowMsg(webSocket, messageType, msg);
}

}

@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
webSocket.close(1000, null);
log.info("Closing: " + code + " / " + reason);
}

@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
log.error("Error: " + t.getMessage());
}

private void dispatchBotMsg(WebSocket webSocket, String messageType, JsonObject msg) {
//发生错误了
if (messageType.equals("BOT_STREAM_FAILURE")) {
String message = GsonUtils.get(msg, "message", "");
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder().projectName(projectName).text(message).type(M78MessageType.failure).id(id).build());
if (null != latch) {
latch.countDown();
}
return;
}

if (messageType.equals("BOT_STREAM_BEGIN")) {
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder().projectName(projectName).text("").type(M78MessageType.begin).id(id).build());
return;
}

if (messageType.equals("BOT_STREAM_RESULT")) {
log.info("BOT_STREAM_RESULT:{}", sb);
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder().projectName(projectName).text(sb.toString()).type(M78MessageType.success).id(id).build());
webSocket.close(1000, null);
if (null != latch) {
latch.countDown();
}
return;
}

if (messageType.equals("BOT_STREAM_EVENT")) {
String message = GsonUtils.get(msg, "message", "");
//解决中文乱码的问题
message = Base64Utils.decodeBase64String(message);
log.info("message:{}", message);
sb.append(message);
//如果被取消了,则不再追加内容了
if (!this.cancel.get()) {
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder().projectName(projectName).code(false).text(message).type(M78MessageType.process).id(id).build());
}
}

if (messageType.equals("BOT_RESULT")) {
String message = GsonUtils.get(msg, "data", "");
log.info("message:{}", message);
if (!this.cancel.get()) {
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder().projectName(projectName).code(false).text("").type(M78MessageType.begin).id(id).build());
consumer.accept(M78Message.builder().projectName(projectName).code(false).text(message).type(M78MessageType.process).id(id).build());
consumer.accept(M78Message.builder().projectName(projectName).code(false).text("").type(M78MessageType.success).id(id).build());
}
}
}

private void dispatchFlowMsg(WebSocket webSocket, String messageType, JsonObject msg) {
if (messageType.equals("FLOW_EXECUTE_STATUS")
|| messageType.equals("FLOW_EXECUTE_FAILURE")) {
String message = msg.toString();
String id = GsonUtils.get(msg, "id", UUID.randomUUID().toString());
consumer.accept(M78Message.builder()
.projectName(projectName)
.text(message)
.category(M78MessageCategory.flow)
.type(M78MessageType.process)
.id(id)
.build());
if (null != latch) {
latch.countDown();
}
return;
}

if (messageType.equals("FLOW_EXECUTE_MESSAGE")) {
// NOT USED FOR NOW
}
}
};
ws = client.newWebSocket(request, listener);
log.info("init finish");
}


//发送消息
public void send(JsonObject req) {
ws.send(req.toString());
}

//发送消息 - 1
public void send(String req) {
ws.send(req);
}

@Override
public String getName() {
return StringUtils.isNotBlank(name) ? name : type.getTypeName();
}

@Override
public ClientType getClientType() {
return type;
}

public static BotWsClient.Builder builder() {
return new BotWsClient.Builder();
}


public static final class Builder {
private String name;
private String projectName;
private String url = "ws://10.38.204.190:8076/ws/bot/abc";
private String token;
private CountDownLatch latch;

private Builder() {
}

public static Builder builder() {
return new Builder();
}

public Builder name(String val) {
name = val;
return this;
}

public Builder projectName(String val) {
projectName = val;
return this;
}

public Builder url(String val) {
url = val;
return this;
}

public Builder token(String val) {
token = val;
return this;
}

public Builder latch(CountDownLatch val) {
latch = val;
return this;
}

public BotWsClient build() {
return new BotWsClient(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package run.mone.m78.client.bot;

import com.google.common.base.Stopwatch;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import run.mone.m78.client.flow.DefaultFlowMsgConsumer;
import run.mone.m78.client.model.M78Message;
import run.mone.m78.client.model.M78MessageType;

import java.util.Map;
import java.util.function.Consumer;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 16:39
*/
@Slf4j
public class DefaultBotMsgConsumer implements Consumer<M78Message> {

private final DefaultFlowMsgConsumer flowMsgConsumer = new DefaultFlowMsgConsumer();

private Stopwatch sw;

public DefaultBotMsgConsumer(Stopwatch sw) {
this.sw = sw;
}

@Override
public void accept(M78Message msg) {
log.info("接收到消息:{}", msg);
switch (msg.getCategory()) {
case bot:
handleBotCategory(msg);
break;
case flow:
handleFlowCategory(msg);
break;
default:
log.warn("未知消息类型:{}, 不做处理", msg.getCategory());
}
}

private void handleBotCategory(M78Message msg) {
doHandleBotCategory(msg);
}


private void handleFlowCategory(M78Message msg) {
if (doHandleFlowCategory(msg)) {
return;
}
flowMsgConsumer.accept(msg);
}

protected void doHandleBotCategory(M78Message msg) {
processGeneratedMsg(msg, Context.builder().build());
}

protected boolean doHandleFlowCategory(M78Message msg) {
return false;
}


protected void processGeneratedMsg(M78Message msg, Context context) {
handleBeginMsg(msg, context);
handleContentMsg(msg, context);
handleEndMsg(msg, context);
handleError(msg, context);
}

protected void handleBeginMsg(@NotNull M78Message msg, Context context) {
log.info("handleBeginMsg: {}, context:{}", msg.getId(), context);
if (msg.getType().equals(M78MessageType.begin)) {
// TODO you may override this
}
}

protected void handleContentMsg(M78Message msg, Context context) {
log.info("handleContentMsg: {}, context:{}", msg.getId(), context);
if (msg.getType().equals(M78MessageType.process)) {
// TODO you may override this
}
}

protected void handleEndMsg(M78Message msg, Context context) {
log.info("handleEndMsg: {}, context:{}", msg.getId(), context);
if (msg.getType().equals(M78MessageType.success)) {
// TODO you may override this
}
}

protected void handleError(M78Message msg, Context context) {
log.info("handleError: {}, context:{}", msg.getId(), context);
if (msg.getType().equals(M78MessageType.failure)) {
// TODO you may override this
}
}

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public static class Context {
private Map<String, Object> context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package run.mone.m78.client.flow;

import lombok.extern.slf4j.Slf4j;
import run.mone.m78.client.model.M78Message;
import run.mone.m78.client.model.M78MessageType;

import java.util.function.Consumer;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 16:40
*/
@Slf4j
public class DefaultFlowMsgConsumer implements Consumer<M78Message> {

@Override
public void accept(M78Message m78Message) {
int msgLength = m78Message.getMessage().length();
log.info("receive m78 message type:{}, size:{}", m78Message.getType().name(), msgLength);
handleFlowMsg(m78Message, m78Message.getType());
}

protected void handleFlowMsg(M78Message m78Message, M78MessageType messageType) {
switch (messageType) {
case process:
handleFlowExecuteStatus(m78Message);
break;
case failure:
handleFlowExecuteFailure(m78Message);
break;
default:
log.warn("message type:{}, not a valid type, will discard!", messageType);
}
}

protected void handleFlowExecuteFailure(M78Message m78Message) {
// send this to visual
// TODO
}

private void handleFlowExecuteStatus(M78Message m78Message) {
// send this to visual
// TODO
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package run.mone.m78.client.flow;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import run.mone.m78.client.model.ClientType;
import run.mone.m78.client.M78Client;
import run.mone.m78.client.model.M78FlowReq;
import run.mone.m78.client.util.GsonUtils;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:36
*/
@Slf4j
public class FlowHttpClient implements M78Client {

private String name = ClientType.FLOW_HTTP.getTypeName();

private String url = "https://mone.test.mi.com/open-apis/ai-plugin-new/feature/router/flow/querySync";

private String token;

private long timeout = 60;

private final ClientType type = ClientType.FLOW_HTTP;

public FlowHttpClient() {
// default constructor
}

private FlowHttpClient(Builder builder) {
name = builder.name;
url = builder.url;
token = builder.token;
timeout = builder.timeout;
}

public static FlowHttpClient.Builder builder() {
return new FlowHttpClient.Builder();
}

public String callFlow(M78FlowReq flowReq, JsonObject jsonObject) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.build();

// 使用 Gson 的 JsonObject 构建请求体
JsonObject mainObject = new JsonObject();
mainObject.addProperty("userName", flowReq.getUserName());
mainObject.addProperty("flowId", flowReq.getFlowId());
mainObject.add("input", GsonUtils.GSON.toJsonTree(flowReq.getInputs()));

if (jsonObject != null) {
if (jsonObject.has("history")) {
JsonElement history = jsonObject.remove("history");
mainObject.add("history", history);
}
if (jsonObject.size() > 0) {
mainObject.add("params", jsonObject);
}
}

String jsonBody = mainObject.toString();

RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonBody);

Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Accept", "application/json, text/plain, */*")
.addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.addHeader("Cache-Control", "no-cache")
.addHeader("Connection", "keep-alive")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", Optional.ofNullable(token).orElseThrow(() -> new IllegalArgumentException("须传递token!")))
.build();

try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (Throwable e) {
log.error("callFlow error", e);
}
return "";
}

@Override
public String getName() {
return StringUtils.isNotBlank(name) ? name : type.getTypeName();
}

@Override
public ClientType getClientType() {
return type;
}

public static final class Builder {

private String name = ClientType.FLOW_HTTP.getTypeName();

private String url = "https://mone.test.mi.com/open-apis/ai-plugin-new/feature/router/flow/querySync";
private String token;

public long timeout = 60;

private Builder() {
}

public static Builder builder() {
return new Builder();
}

public Builder name(String val) {
name = val;
return this;
}

public Builder url(String val) {
url = val;
return this;
}

public Builder token(String val) {
token = val;
return this;
}

public Builder timeout(long val) {
timeout = val;
return this;
}

public FlowHttpClient build() {
return new FlowHttpClient(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package run.mone.m78.client.flow;

import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import org.apache.commons.lang3.StringUtils;
import run.mone.m78.client.M78Client;
import run.mone.m78.client.model.ClientType;
import run.mone.m78.client.model.M78FlowCMD;
import run.mone.m78.client.model.M78FlowOperateType;
import run.mone.m78.client.model.M78FlowReq;
import run.mone.m78.client.model.M78Message;
import run.mone.m78.client.model.M78MessageCategory;
import run.mone.m78.client.model.M78MessageType;
import run.mone.m78.client.util.GsonUtils;

import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
* @author HawickMason@xiaomi.com
* @date 8/11/24 17:47
*/
@Slf4j
public class FlowWsClient implements M78Client {

private String name = ClientType.FLOW_WS.getTypeName();

private ClientType type = ClientType.FLOW_WS;

private WebSocket ws;

private String projectName;

private String url = "ws://10.38.204.190:8076/ws/flow/stream/access";


private CountDownLatch latch;

private FlowWsClient(Builder builder) {
projectName = builder.projectName;
url = builder.url;
latch = builder.latch;
}

/**
* 初始化WebSocket连接并处理消息
*
* @param consumer 消息消费者,用于处理接收到的消息
* @param userName 用户名,用于请求头中的x-account字段
*/
public void start(Consumer<M78Message> consumer, String userName) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.header("x-account", userName)
.build();

StringBuilder sb = new StringBuilder();

WebSocketListener listener = new WebSocketListener() {

Stopwatch sw = Stopwatch.createStarted();

private AtomicBoolean cancel = new AtomicBoolean(false);


@Override
public void onOpen(WebSocket webSocket, Response response) {
log.info("ws open");
onClientInit(webSocket, response, consumer);
}

@Override
public void onMessage(WebSocket webSocket, String text) {
log.info("Received:{}", text);
onMessageReceived(webSocket, text, consumer);
}

@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
log.info("M78WsClient Closing, code: {}, reason:{}", code, reason);
onClientClose(webSocket, code, reason, consumer);
webSocket.close(1000, null);
}

@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
onClientError(webSocket, t, response, consumer);
log.error("M78WsClient Error: {}, response:{}", t.getMessage(), response.toString());
}
};
ws = client.newWebSocket(request, listener);
log.info("init finish");
}


/**
* 客户端初始化方法,可以被重写
*
* @param webSocket WebSocket对象
* @param response 响应对象
* @param consumer 消费者对象,用于处理M78Message消息
*/
public void onClientInit(WebSocket webSocket, Response response, Consumer<M78Message> consumer) {
// you may override this
}

/**
* 处理接收到的WebSocket消息, 可以被重写
* 默认行为是将消息处理委托给 Consumer<M78Message> consumer
*
* @param webSocket WebSocket对象
* @param text 接收到的消息文本
* @param consumer 消费处理M78Message对象的消费者
*/
public void onMessageReceived(WebSocket webSocket, String text, Consumer<M78Message> consumer) {
// you may override this
JsonObject msg = JsonParser.parseString(text).getAsJsonObject();
consumer.accept(M78Message.builder()
.text(msg.toString())
.category(M78MessageCategory.flow)
.type(M78MessageType.process)
.build());
}

/**
* 当客户端关闭连接时调用此方法, 可以被重写
*
* @param webSocket WebSocket对象,表示客户端连接
* @param code 关闭连接的状态码
* @param reason 关闭连接的原因
* @param consumer 消费者对象,用于处理M78Message消息
*/
public void onClientClose(WebSocket webSocket, int code, String reason, Consumer<M78Message> consumer) {
// you may override this
}

/**
* 处理WebSocket客户端错误的回调方法,可以被重写
*
* @param webSocket WebSocket对象
* @param t 异常对象
* @param response 响应对象
* @param consumer 消费者对象,用于处理M78Message
*/
public void onClientError(WebSocket webSocket, Throwable t, Response response, Consumer<M78Message> consumer) {
// you may override this
}

public void executeFlow(String userName, String flowId, Map<String, Object> inputs) {
try {
M78FlowReq req = M78FlowReq.builder()
.userName(userName)
.flowId(flowId)
.inputs(inputs)
.operateCmd(M78FlowOperateType.TEST_FLOW)
.build();
operateFlow(req);
} catch (Throwable e) {
log.error("Error executing flow:", e);
}
}

public void resumeFlow(String userName, String flowId, String flowRecordId) {
try {
M78FlowReq req = M78FlowReq.builder()
.userName(userName)
.flowId(flowId)
.flowRecordId(flowRecordId)
.operateCmd(M78FlowOperateType.OPERATE_FLOW)
.cmd(M78FlowCMD.MANUAL_CONFIRM_FLOW)
.build();
operateFlow(req);
} catch (Throwable e) {
log.error("resumeFlow error:", e);
}
}

public void terminateFlow(String userName, String flowId, String flowRecordId) {
try {
M78FlowReq req = M78FlowReq.builder()
.userName(userName)
.flowId(flowId)
.flowRecordId(flowRecordId)
.operateCmd(M78FlowOperateType.OPERATE_FLOW)
.cmd(M78FlowCMD.CANCEL_FLOW)
.build();
operateFlow(req);
} catch (Throwable e) {
log.error("Failed to terminate flow:", e);
}
}

public void getFlowStatus(String userName, String flowId, String flowRecordId) {
try {
M78FlowReq req = M78FlowReq.builder()
.userName(userName)
.flowId(flowId)
.flowRecordId(flowRecordId)
.operateCmd(M78FlowOperateType.GET_STATUS)
.build();
operateFlow(req);
} catch (Throwable e) {
log.error("getFlowStatus error", e);
}
}

protected void operateFlow(@Nonnull M78FlowReq req) {
try {
if (StringUtils.isBlank(req.getOperateCmd())) {
log.error("empty operateCmd! WILL DO NOTHING!");
return;
}
send(GsonUtils.GSON.toJson(req));
} catch (Throwable e) {
log.error("Error operating flow", e);
}
}

//发送消息
protected void send(String req) {
ws.send(req);
}

private static String get(JsonObject obj, String key, String defaultValue) {
if (obj.has(key)) {
return obj.get(key).getAsString();
}
return defaultValue;
}

@Override
public String getName() {
return StringUtils.isNotBlank(name) ? name : type.getTypeName();
}

@Override
public ClientType getClientType() {
return type;
}

public static FlowWsClient.Builder builder() {
return new FlowWsClient.Builder();
}

public static final class Builder {
private String projectName;
private String url = "ws://10.38.204.190:8076/ws/flow/stream/access";
private CountDownLatch latch;

private Builder() {
}


public Builder projectName(String val) {
projectName = val;
return this;
}

public Builder url(String val) {
url = val;
return this;
}

public Builder latch(CountDownLatch val) {
latch = val;
return this;
}

public FlowWsClient build() {
return new FlowWsClient(this);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package run.mone.m78.client.model;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 10:37
*/
public enum ClientType {

UNKNOWN(-1, "unknown"),
BOT_HTTP(0, "bot_http"),
BOT_WS(1, "bot_ws"),

FLOW_HTTP(2, "flow_http"),

FLOW_WS(3, "flow_ws");

private final int code;
private final String typeName;

private static final Map<Integer, ClientType> valMap = Arrays.stream(values()).collect(Collectors.toMap(ClientType::getCode, Function.identity()));

private static final Map<String, ClientType> nameMap = Arrays.stream(values()).collect(Collectors.toMap(ClientType::getTypeName, Function.identity()));

ClientType(int code, String typeName) {
this.code = code;
this.typeName = typeName;
}

public int getCode() {
return code;
}

public String getTypeName() {
return typeName;
}

public static ClientType getTypeByCode(int code) {
return valMap.getOrDefault(code, UNKNOWN);
}

public static ClientType getTypeByName(String name) {
return nameMap.getOrDefault(name, UNKNOWN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package run.mone.m78.client.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* @author goodjava@qq.com
* @date 2024/9/10 12:58
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class History {

private List<Message> messages;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package run.mone.m78.client.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 15:24
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class M78BotReq {

private String botId;

private String userName;

private String input;

private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package run.mone.m78.client.model;

import lombok.Data;

/**
* @author goodjava@qq.com
* @date 2024/9/10 10:14
*/
@Data
public class M78Data<D> {

private int code;

private String message;

private D data;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package run.mone.m78.client.model;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 14:43
*/
public class M78FlowCMD {

public static final String CANCEL_FLOW = "cancelFlow";

public static final String MANUAL_CONFIRM_FLOW = "manualConfirmFlow";

public static final String GOTO_FLOW = "gotoFlow";

public static final String MODIFY_PARAM = "modifyParam";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package run.mone.m78.client.model;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 14:40
*/
public class M78FlowOperateType {

public static final String GET_STATUS = "getStatus";

public static final String TEST_FLOW = "testFlow";

public static final String OPERATE_FLOW = "operateFlow";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package run.mone.m78.client.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Map;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 14:38
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class M78FlowReq {

private String userName;

private String flowId;

private String flowRecordId;

private String operateCmd;

private String cmd;

private Map<String, Object> inputs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package run.mone.m78.client.model;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
* @author goodjava@qq.com
* @date 2023/6/9 14:32
*/
@Data
@Builder
public class M78Message implements Serializable {

private M78MessageType type;

private String message;

@Builder.Default
private M78MessageCategory category = M78MessageCategory.bot;

private String id;

private String text;

private String projectName;

//代表是否是编码(```code```)
@Builder.Default
private boolean code = true;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package run.mone.m78.client.model;

/**
* @author HawickMason@xiaomi.com
* @date 8/15/24 17:16
*/
public enum M78MessageCategory {
bot,
flow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package run.mone.m78.client.model;

/**
* @author goodjava@qq.com
* @date 2023/6/9 14:33
*/
public enum M78MessageType {

begin,
process,
success,
failure,

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package run.mone.m78.client.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author goodjava@qq.com
* @date 2024/9/10 12:59
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Message {

private String role;

private String content;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package run.mone.m78.client.util;

import java.nio.charset.Charset;
import java.util.Base64;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 14:34
*/
public class Base64Utils {

public static String decodeBase64String(String str) {
return new String(Base64.getDecoder().decode(str.getBytes(Charset.forName("utf8"))), Charset.forName("utf8"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package run.mone.m78.client.util;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import java.util.function.Supplier;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 14:35
*/
public class GsonUtils {

public static final Gson GSON = new Gson();


public static boolean get(JsonObject obj, String key, boolean defaultValue) {
if (obj.has(key)) {
return Boolean.valueOf(obj.get(key).getAsString());
}
return defaultValue;
}

public static String get(JsonObject obj, String key, String defaultValue) {
if (obj.has(key)) {
return obj.get(key).getAsString();
}
return defaultValue;
}

public static String get(JsonObject obj, String key, Supplier<String> supplier) {
if (obj.has(key)) {
return obj.get(key).getAsString();
}
return supplier.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package run.mone.m78.client.test;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import run.mone.m78.client.bot.BotHttpClient;
import run.mone.m78.client.model.History;
import run.mone.m78.client.model.M78BotReq;
import run.mone.m78.client.model.Message;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* @author HawickMason@xiaomi.com
* @date 8/22/24 15:57
*/
public class BotHttpClientTest {

@Test
public void testBotHttpClient() {
BotHttpClient client = BotHttpClient.builder()
// .url("http://127.0.0.1:8077/open-apis/v1/ai-plugin-new/feature/router/probot/query")
.token("e1c51eaa-2f39-4cbc-8ec0-b6ab9d117a02").build();
String res = client.callBot(M78BotReq.builder()
.botId("100093")
.userName("wangyingjie3")
.input("北京今天的天气?")
.build(), null);
System.out.println(res);
}

@Test
public void testBotHttpClientWithHistory() {
BotHttpClient client = BotHttpClient.builder()
// .url("http://127.0.0.1:8077/open-apis/v1/ai-plugin-new/feature/router/probot/query")
.token("e1c51eaa-2f39-4cbc-8ec0-b6ab9d117a02").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("desc", " ");
jsonObject.addProperty("input", "a+b=?");
jsonObject.add("history", new Gson().toJsonTree(Lists.newArrayList(
ImmutableMap.of("role", "user", "content", "a=1"),
ImmutableMap.of("role", "user", "content", "b=2")
)));
String res = client.callBot(M78BotReq.builder()
.botId("130228")
.userName("wangyingjie3")
.input("hi")
.build(), jsonObject, BotHttpClient.DEFAULT_FUNCTION);
System.out.println(res);
}

@Test
public void testBotHttpClientWithHistory2() {
BotHttpClient client = BotHttpClient.builder()
.url("http://127.0.0.1:8077/open-apis/v1/ai-plugin-new/feature/router/probot/query")
.token("e1c51eaa-2f39-4cbc-8ec0-b6ab9d117a02").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("desc", "");
jsonObject.addProperty("input", "a+b=?");
String res = client.callBot(M78BotReq.builder()
.botId("130228")
.userName("wangyingjie3")
.input("hi")
.build(), jsonObject, History.builder()
.messages(Lists.newArrayList(Message.builder().role("user").content("a=1").build(),
Message.builder().role("user").content("b=2").build())).build(), BotHttpClient.DEFAULT_FUNCTION);
System.out.println(res);
}


@Test
public void testBotHttpClientWithHistory3() {
List<String> list = new ArrayList<>();
// String topic = "11.2 为什么大于 11.11";
// String topic = "陆地上最大的生物是?";
// String topic = "如何写好代码?";
// String topic = "一个博客系统有那些必要的模块";
// String topic = "人如何才能长寿";
String topic = "书店系统前后端接口如何对齐,讨论都需要哪些接口";
String input = "我们来讨论下:" + topic;
int round = 5;
for (int i = 0; i < round; i++) {
String res = callBotWithInput("小明", input, list.stream().collect(Collectors.joining("\n")), topic, round, i + 1, "你是一名vue前端", "1.你需要列出后端给你提供的接口描述 2.你会充分采纳前端的意见 3.如果有些接口你觉得你实现更好,你可以建议给后端");
if (StringUtils.isNotEmpty(input)) {
list.add("小明:" + input);
input = "";
}
list.add("小红:" + res);
res = callBotWithInput("小红", "", list.stream().collect(Collectors.joining("\n")), topic, round, i + 1, "你是一名java后端", "1.你会列出具体的接口,比如/book/add 2.你会采纳前端提的需求,并给他生成接口 3.如果前端要求你去掉某个接口,你尽量同意 4.列出接口");
list.add("小明:" + res);
}

}

private static String callBotWithInput(String name, String input, String history, String topic, int round, int curRound, String character_setting, String main_points) {
BotHttpClient client = BotHttpClient.builder()
.url("http://127.0.0.1:8077/open-apis/v1/ai-plugin-new/feature/router/probot/query")
.token("e1c51eaa-2f39-4cbc-8ec0-b6ab9d117a02").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("team", "小明 小红");
jsonObject.addProperty("_history", history);
jsonObject.addProperty("topic", topic);
jsonObject.addProperty("desc", "");
jsonObject.addProperty("name", name);
jsonObject.addProperty("character_setting", character_setting);
jsonObject.addProperty("main_points", main_points);
jsonObject.addProperty("input", input);
jsonObject.addProperty("round", round);
jsonObject.addProperty("cur_round", curRound);

String res = client.callBot(M78BotReq.builder()
.token("61d8661d-0663-48b4-8da3-40c08b0e8453")
.botId("130228")
.userName("wangyingjie3")
.input("")
.build(), jsonObject, BotHttpClient.DEFAULT_FUNCTION);
return res;
}


@Test
public void testBotHttpClient2() {
BotHttpClient client = BotHttpClient.builder().url("http://127.0.0.1:8077/open-apis/v1/ai-plugin-new/feature/router/probot/query").token("6804ec8c-e348-4f36-a881-dd4e8741a3ab").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("agent", "1.教师 2.日语翻译 3.程序员 4.客服 5.测试 ");
jsonObject.addProperty("input", "有个文档要翻译成日语");
String res = client.callBot(M78BotReq.builder()
.botId("160222")
.userName("zhangzhiyong1")
.input("")
.build(), jsonObject);
System.out.println(res);
}

@Test
public void testBotJsonFix() {
BotHttpClient client = BotHttpClient.builder().url("http://10.38.204.190:8076/open-apis/v1/ai-plugin-new/feature/router/probot/query").token("").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("json", "[\"name\":\"123\"]\n");
String res = client.callBot(M78BotReq.builder()
.botId("130244")
.userName("zhangzhiyong1")
.input("")
.build(), jsonObject);
System.out.println(res);
}

@Test
public void testBotJsonReq() {
BotHttpClient client = BotHttpClient.builder()
.url("http://10.38.204.190:8076/open-apis/v1/ai-plugin-new/feature/router/probot/query")
.token("6804ec8c-e348-4f36-a881-dd4e8741a3ab").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("request", "下雨了\n" +
"{\"weather\":\"\"}");
String res = client.callBot(M78BotReq.builder()
.botId("160224")
.userName("zhangzhiyong1")
.input("")
.build(), jsonObject);
System.out.println(res);
}

@Test
public void testFlowChoose() {
BotHttpClient client = BotHttpClient.builder().url("http://10.38.204.190:8076/open-apis/v1/ai-plugin-new/feature/router/probot/query").token("").build();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("flow", "2.读取代办事项\n" +
"3.编写业务代码\n" +
"6.制定健身计划\n");
jsonObject.addProperty("input", "帮我开发一个网站");
String res = client.callBot(M78BotReq.builder()
.botId("160223")
.userName("zhangzhiyong1")
.input("")
.build(), jsonObject);
System.out.println(res);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package run.mone.m78.client.test;

import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import run.mone.m78.client.bot.BotWsClient;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
* @author HawickMason@xiaomi.com
* @date 9/11/24 11:03
*/
public class BotWsClientTest {

@Test
public void testBotWsClient() throws InterruptedException {
String botId = "100265";
String token = "ac6524cf-6053-4888-8464-40b3fb04dc92";
String input = "你好,1+1等于几呀?";
BotWsClient botWsClient = BotWsClient.builder()
.url("ws://10.38.204.190:8076/ws/bot/abc")
.token(token)
.build();
botWsClient.start(System.out::println);

JsonObject jsonReq = new JsonObject();
jsonReq.addProperty("botId", botId);
jsonReq.addProperty("token", token);
jsonReq.addProperty("input", input);
jsonReq.addProperty("topicId", UUID.randomUUID().toString());
botWsClient.send(jsonReq.toString());
TimeUnit.SECONDS.sleep(20);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package run.mone.m78.client.test;

import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Test;
import run.mone.m78.client.bot.BotHttpClient;
import run.mone.m78.client.flow.FlowHttpClient;
import run.mone.m78.client.model.M78BotReq;
import run.mone.m78.client.model.M78FlowReq;

/**
* @author HawickMason@xiaomi.com
* @date 8/26/24 15:04
*/
public class FLowHttpClientTest {
@Test
public void testBotHttpClient() {
FlowHttpClient client = FlowHttpClient.builder().token("e1c51eaa-2f39-4cbc-8ec0-b6ab9d117a02").build();
String res = client.callFlow(M78FlowReq.builder()
.flowId("127")
.userName("wangyingjie3")
.inputs(ImmutableMap.of("scale", "256"))
.build(), null);
System.out.println(res);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package run.mone.m78.client.test;

import org.junit.jupiter.api.Test;
import run.mone.m78.client.flow.FlowWsClient;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

/**
* @author HawickMason@xiaomi.com
* @date 8/26/24 2:18 PM
*/
public class FlowWsClientTest {

/**
* 测试执行流程的方法,连通性测试,本地m78会报找不到agent
*
* @throws InterruptedException 如果线程在等待、睡眠或占用时被中断
*/
@Test
public void testExecuteFlow() throws InterruptedException {
FlowWsClient flowWsClient = FlowWsClient.builder().build();
Map<String, Object> inputs = new HashMap<>();
inputs.put("scale", "256");
String userName = "wangyingjie3";
assertDoesNotThrow(() -> {
flowWsClient.start(System.out::println, userName);
flowWsClient.executeFlow(userName, "127", inputs);
});
TimeUnit.SECONDS.sleep(20);
}
}
12 changes: 11 additions & 1 deletion jcommon/hive/pom.xml
Original file line number Diff line number Diff line change
@@ -38,6 +38,12 @@
<version>2.11.0</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.15.3</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@@ -83,7 +89,11 @@
<version>3.15.4.RELEASE</version>
</dependency>


<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.25.8</version>
</dependency>

</dependencies>

9 changes: 8 additions & 1 deletion jcommon/hive/src/main/java/run/mone/hive/Team.java
Original file line number Diff line number Diff line change
@@ -18,6 +18,9 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

@Slf4j
@@ -27,6 +30,10 @@ public class Team {
private static final String MESSAGE_ROUTE_TO_ALL = "*";
private static final Path SERDESER_PATH = Path.of("storage");

private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 10,
10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(50));

private Environment env;
private double investment;
private String idea;
@@ -111,7 +118,7 @@ public CompletableFuture<List<Message>> run(int _nRound, String idea, String sen
log.error("Error running team: {}", e.getMessage());
throw new RuntimeException("Team execution failed", e);
}
});
}, executor);
}

public void serialize(Path stgPath) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package run.mone.hive.actions;

public class ActionFactory {

public static Action createAction(String actionType) {
return switch (actionType.toLowerCase()) {
case "writecode" -> new WriteCode();
case "writeprd" -> new WritePRD();
case "writetest" -> new WriteTest();
case "writedesign" -> new WriteDesign();
case "debugerror" -> new DebugError();
case "runcode" -> new RunCode();
case "summarizecode" -> new SummarizeCode();
default -> throw new IllegalArgumentException("Unknown action type: " + actionType);
};
}

public static boolean isValidActionType(String actionType) {
try {
createAction(actionType);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@

package run.mone.hive.planner;

import run.mone.hive.Team;
import run.mone.hive.actions.ActionFactory;
import run.mone.hive.context.Context;
import run.mone.hive.llm.LLM;
import run.mone.hive.roles.Role;
import run.mone.hive.roles.RoleFactory;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class OptimalTeamStrategy implements PlanningStrategy {

private final Context context;
private final LLM llm;
private final Map<String, List<String>> roleToActionsMap;
private TeamBuilder teamBuilder;

public OptimalTeamStrategy(TeamBuilder teamBuilder, Context context, LLM llm) {
this.teamBuilder = teamBuilder;
this.context = context;
this.llm = llm;
this.roleToActionsMap = new HashMap<>();
initializeRoleToActionsMap();
}

@Override
public Team planTeam(String requirement) {
return this.buildTeam(requirement);
}

private void initializeRoleToActionsMap() {
// 为每种角色预设可能需要的actions
roleToActionsMap.put("engineer", Arrays.asList(
"writecode", "writetest", "debugerror", "runcode"
));

roleToActionsMap.put("productmanager", Arrays.asList(
"writeprd", "writereview"
));

roleToActionsMap.put("architect", Arrays.asList(
"writedesign", "reviewcode"
));

roleToActionsMap.put("qaengineer", Arrays.asList(
"writetest", "runcode", "debugerror"
));
}

public Team buildTeam(String requirement) {
// 创建新的team实例
Team team = new Team(context);

// 分析需求,确定需要的角色
Set<String> requiredRoles = analyzeRequirement(requirement);

// 为每个需要的角色创建实例
for (String roleType : requiredRoles) {
Role role = RoleFactory.createRole(roleType, llm);

// 为角色分配合适的actions
List<String> actionTypes = roleToActionsMap.get(roleType.toLowerCase());
if (actionTypes != null) {
for (String actionType : actionTypes) {
if (ActionFactory.isValidActionType(actionType)) {
role.getActions().add(ActionFactory.createAction(actionType));
}
}
}

// 将角色添加到team中
team.hire(Collections.singletonList(role));
}

// 记录team
teamBuilder.rememberTeam(requirement, team);

return team;
}

private Set<String> analyzeRequirement(String requirement) {
// TODO:这里可以使用LLM来分析需求,确定需要哪些角色
// 简单起见,这里使用一些关键词匹配
Set<String> roles = new HashSet<>();

requirement = requirement.toLowerCase();

if (requirement.contains("code") || requirement.contains("develop") ||
requirement.contains("implement")) {
roles.add("engineer");
}

if (requirement.contains("design") || requirement.contains("architect")) {
roles.add("architect");
}

if (requirement.contains("test") || requirement.contains("quality")) {
roles.add("qaengineer");
}

if (requirement.contains("product") || requirement.contains("requirement")) {
roles.add("productmanager");
}

// 确保至少有一个角色
if (roles.isEmpty()) {
roles.add("engineer"); // 默认添加工程师角色
}

return roles;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

package run.mone.hive.planner;

import run.mone.hive.Team;

public interface PlanningStrategy {
Team planTeam(String requirement);
}
37 changes: 37 additions & 0 deletions jcommon/hive/src/main/java/run/mone/hive/planner/TeamBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package run.mone.hive.planner;

import run.mone.hive.Team;
import run.mone.hive.actions.ActionFactory;
import run.mone.hive.roles.Role;
import run.mone.hive.roles.RoleFactory;
import run.mone.hive.context.Context;
import run.mone.hive.llm.LLM;

import java.util.*;

public class TeamBuilder {

private PlanningStrategy planningStrategy; // 可以扩展为不同的策略
private Map<String, Team> teams;

public TeamBuilder(Context context, LLM llm) {
this.planningStrategy = new OptimalTeamStrategy(this, context, llm);
this.teams = new HashMap<>();
}

public void rememberTeam(String task, Team team) {
teams.put(task, team);
}

public void setPlanningStrategy(PlanningStrategy planningStrategy) {
this.planningStrategy = planningStrategy;
}

public Team planTeam(String requirement) {
if (teams.containsKey(requirement)) {
return teams.get(requirement);
}
return planningStrategy.planTeam(requirement);
}

}
16 changes: 16 additions & 0 deletions jcommon/hive/src/main/java/run/mone/hive/roles/Role.java
Original file line number Diff line number Diff line change
@@ -36,6 +36,8 @@ public class Role {
protected String goal;

protected String constraints;
@Getter
protected List<String> specializations;

@JsonIgnore
protected Planner planner;
@@ -93,6 +95,17 @@ public Role(String name, String profile, String goal, String constraints, Consum
init();
}

public Role(String name, String profile, String goal, String constraints, List<String> specializations) {
this.name = name;
this.profile = profile;
this.goal = goal;
this.constraints = constraints;
this.specializations = specializations;
this.actions = new ArrayList<>();
this.watchList = new HashSet<>();
init();
}

// 初始化方法
protected void init() {
this.rc = new RoleContext(profile);
@@ -170,6 +183,9 @@ protected boolean isRelevantMessage(Message message) {
message.getReceivers().contains(name);
}

public boolean isCompatibleWithTask(String task) {
return specializations.stream().anyMatch(task.toLowerCase()::contains);
}

// 创建规划器
protected Planner createPlanner() {
28 changes: 28 additions & 0 deletions jcommon/hive/src/main/java/run/mone/hive/roles/RoleFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package run.mone.hive.roles;

import run.mone.hive.roles.*;
import run.mone.hive.llm.LLM;

public class RoleFactory {

public static Role createRole(String roleType, LLM llm) {
return switch (roleType.toLowerCase()) {
case "engineer" -> new Engineer();
// case "productmanager" -> new ProductManager();
case "architect" -> new Architect();
// case "qaengineer" -> new QaEngineer();
case "writer" -> new Writer("Writer");
case "teacher" -> new Teacher(null);
default -> throw new IllegalArgumentException("Unknown role type: " + roleType);
};
}

public static boolean isValidRoleType(String roleType) {
try {
createRole(roleType, null);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
}
242 changes: 242 additions & 0 deletions jcommon/hive/src/main/java/run/mone/hive/tools/ToolConvertor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package run.mone.hive.tools;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import lombok.extern.slf4j.Slf4j;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.javadoc.Javadoc;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

@Slf4j
public class ToolConvertor {

public static Map<String, Object> convertCodeToToolSchema(Object obj, List<String> include) {
if (obj == null) {
return new HashMap<>();
}

Class<?> clazz = obj instanceof Class ? (Class<?>) obj : obj.getClass();
String docString = getClassJavadoc(clazz);

Map<String, Object> schema = new HashMap<>();

if (obj instanceof Class) {
schema.put("type", "class");
schema.put("description", cleanDocString(docString));

Map<String, Object> methods = new HashMap<>();
for (Method method : clazz.getDeclaredMethods()) {
if (shouldSkipMethod(method, include)) {
continue;
}
String methodDoc = getMethodJavadoc(method);
if (methodDoc != null) {
methods.put(method.getName(), functionDocstringToSchema(method, methodDoc));
}
}
schema.put("methods", methods);
} else {
schema = functionDocstringToSchema(obj.getClass().getDeclaredMethods()[0], docString);
}

return schema;
}

public static Map<String, Map<String, Object>> convertCodeToToolSchemaAst(String code) {
Map<String, Map<String, Object>> schemas = new HashMap<>();

try {
JavaParser parser = new JavaParser();
CompilationUnit cu = parser.parse(code).getResult().orElse(null);
if (cu == null) {
return schemas;
}

CodeVisitor visitor = new CodeVisitor(code);
visitor.visit(cu, null);
schemas = visitor.getToolSchemas();

} catch (Exception e) {
log.error("Failed to parse code: {}", e.getMessage());
}

return schemas;
}

private static Map<String, Object> functionDocstringToSchema(Method method, String docString) {
Map<String, Object> schema = new HashMap<>();

schema.put("type", method.isSynthetic() ? "async_function" : "function");

Javadoc javadoc = StaticJavaParser.parseJavadoc(docString);

schema.put("description", javadoc.getDescription().toText().trim());
schema.put("signature", getFunctionSignature(method));

Map<String, String> params = new HashMap<>();
javadoc.getBlockTags().forEach(tag -> {
if (tag.getTagName().equals("param")) {
String[] parts = tag.getContent().toText().split("\\s+", 2);
if (parts.length == 2) {
params.put(parts[0], parts[1].trim());
}
}
});
schema.put("parameters", params);

return schema;
}

private static String getFunctionSignature(Method method) {
StringBuilder signature = new StringBuilder();
signature.append("(");

Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
signature.append(param.getType().getSimpleName())
.append(" ")
.append(param.getName());
if (i < parameters.length - 1) {
signature.append(", ");
}
}

signature.append(") -> ")
.append(method.getReturnType().getSimpleName());

return signature.toString();
}

private static boolean shouldSkipMethod(Method method, List<String> include) {
String name = method.getName();
return (name.startsWith("_") && !name.equals("__init__")) ||
(include != null && !include.contains(name));
}

private static String getClassJavadoc(Class<?> clazz) {
try {
return clazz.getAnnotation(Documentation.class).value();
} catch (Exception e) {
return "";
}
}

private static String getMethodJavadoc(Method method) {
try {
return method.getAnnotation(Documentation.class).value();
} catch (Exception e) {
return null;
}
}

private static String cleanDocString(String docString) {
return docString.replaceAll("\\s+", " ").trim();
}
}

@Slf4j
class CodeVisitor {
private final String sourceCode;
private final Map<String, Map<String, Object>> toolSchemas = new HashMap<>();

public CodeVisitor(String sourceCode) {
this.sourceCode = sourceCode;
}

public void visit(CompilationUnit cu, Void arg) {
cu.findAll(ClassOrInterfaceDeclaration.class).forEach(this::visitClass);
cu.findAll(MethodDeclaration.class).forEach(this::visitMethod);
}

private void visitClass(ClassOrInterfaceDeclaration n) {
Map<String, Object> classSchemas = new HashMap<>();
classSchemas.put("type", "class");
classSchemas.put("description", cleanJavadoc(n.getJavadoc().orElse(null)));

Map<String, Object> methods = new HashMap<>();
n.getMethods().forEach(method -> {
if (!shouldSkipMethod(method)) {
methods.put(method.getNameAsString(), getMethodSchema(method));
}
});

classSchemas.put("methods", methods);
classSchemas.put("code", n.toString());
toolSchemas.put(n.getNameAsString(), classSchemas);
}

private void visitMethod(MethodDeclaration n) {
if (shouldSkipMethod(n)) {
return;
}

Map<String, Object> methodSchema = getMethodSchema(n);
methodSchema.put("code", n.toString());
toolSchemas.put(n.getNameAsString(), methodSchema);
}

private Map<String, Object> getMethodSchema(MethodDeclaration method) {
Map<String, Object> schema = new HashMap<>();

schema.put("type", method.isDefault() ? "async_function" : "function");
schema.put("description", cleanJavadoc(method.getJavadoc().orElse(null)));
schema.put("signature", getMethodSignature(method));
schema.put("parameters", parseJavadocParameters(method.getJavadoc().orElse(null)));

return schema;
}

private String getMethodSignature(MethodDeclaration method) {
return method.getDeclarationAsString(false, false, true);
}

private boolean shouldSkipMethod(MethodDeclaration method) {
String name = method.getNameAsString();
return name.startsWith("_") && !name.equals("__init__");
}

private String cleanJavadoc(com.github.javaparser.javadoc.Javadoc javadoc) {
if (javadoc == null) {
return "";
}
return javadoc.getDescription().toText().replaceAll("\\s+", " ").trim();
}

private Map<String, String> parseJavadocParameters(com.github.javaparser.javadoc.Javadoc javadoc) {
Map<String, String> params = new HashMap<>();
if (javadoc == null) {
return params;
}

// Parse @param and @return tags
javadoc.getBlockTags().forEach(tag -> {
String tagName = tag.getTagName();
String content = tag.getContent().toText();

if ("param".equals(tagName)) {
String[] parts = content.split("\\s+", 2);
if (parts.length == 2) {
params.put(parts[0], parts[1].trim());
}
} else if ("return".equals(tagName)) {
params.put("return", content.trim());
}
});

return params;
}

public Map<String, Map<String, Object>> getToolSchemas() {
return toolSchemas;
}
}

@interface Documentation {
String value();
}
216 changes: 216 additions & 0 deletions jcommon/hive/src/main/java/run/mone/hive/tools/ToolRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package run.mone.hive.tools;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Data
public class ToolRegistry {
private Map<String, Tool> tools = new ConcurrentHashMap<>();
private Map<String, Map<String, Tool>> toolsByTags = new ConcurrentHashMap<>();

private static final ToolRegistry INSTANCE = new ToolRegistry();
private static final String TOOL_SCHEMA_PATH = "schemas/tools/";
private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

private ToolRegistry() {}

public static ToolRegistry getInstance() {
return INSTANCE;
}

public void registerTool(
String toolName,
String toolPath,
Map<String, Object> schemas,
String schemaPath,
String toolCode,
List<String> tags,
Object toolSourceObject,
List<String> includeFunctions,
boolean verbose
) {
if (hasTool(toolName)) {
return;
}

schemaPath = schemaPath.isEmpty() ?
TOOL_SCHEMA_PATH + toolName + ".yml" : schemaPath;

if (schemas == null) {
schemas = makeSchema(toolSourceObject, includeFunctions, schemaPath);
}

if (schemas == null || schemas.isEmpty()) {
return;
}

schemas.put("tool_path", toolPath);

try {
validateSchema(schemas);
} catch (Exception e) {
log.warn("Schema validation failed for {}: {}", toolName, e.getMessage());
}

tags = tags != null ? tags : new ArrayList<>();
Tool tool = new Tool(toolName, toolPath, schemas, toolCode, tags);
tools.put(toolName, tool);

for (String tag : tags) {
toolsByTags.computeIfAbsent(tag, k -> new HashMap<>())
.put(toolName, tool);
}

if (verbose) {
log.info("{} registered", toolName);
log.info("Schema made at {}, can be used for checking", schemaPath);
}
}

public boolean hasTool(String key) {
return tools.containsKey(key);
}

public Tool getTool(String key) {
return tools.get(key);
}

public Map<String, Tool> getToolsByTag(String tag) {
return toolsByTags.getOrDefault(tag, new HashMap<>());
}

public Map<String, Tool> getAllTools() {
return tools;
}

public boolean hasToolTag(String tag) {
return toolsByTags.containsKey(tag);
}

public List<String> getToolTags() {
return new ArrayList<>(toolsByTags.keySet());
}

private Map<String, Object> makeSchema(Object toolSourceObject, List<String> include, String path) {
try {
Files.createDirectories(Paths.get(path).getParent());
Map<String, Object> schema = ToolConvertor.convertCodeToToolSchema(
toolSourceObject, include);
yamlMapper.writeValue(new File(path), schema);
return schema;
} catch (Exception e) {
log.error("Failed to make schema: {}", e.getMessage());
return new HashMap<>();
}
}

private void validateSchema(Map<String, Object> schema) {
// TODO: Implement schema validation
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RegisterTool {
String[] tags() default {};
String schemaPath() default "";
}

@Data
public static class Tool {
private String name;
private String path;
private Map<String, Object> schemas;
private String code;
private List<String> tags;

public Tool(String name, String path, Map<String, Object> schemas,
String code, List<String> tags) {
this.name = name;
this.path = path;
this.schemas = schemas;
this.code = code;
this.tags = tags;
}
}

public static Map<String, Tool> validateToolNames(List<String> tools) {
if (tools == null) {
throw new IllegalArgumentException("tools must be a list");
}

Map<String, Tool> validTools = new HashMap<>();
for (String key : tools) {
if (new File(key).isDirectory() || new File(key).isFile()) {
validTools.putAll(registerToolsFromPath(key));
} else if (INSTANCE.hasTool(key)) {
validTools.put(key, INSTANCE.getTool(key));
} else if (INSTANCE.hasToolTag(key)) {
validTools.putAll(INSTANCE.getToolsByTag(key));
} else {
log.warn("Invalid tool name or tool type name: {}, skipped", key);
}
}
return validTools;
}

public static Map<String, Tool> registerToolsFromPath(String path) {
Map<String, Tool> toolsRegistered = new HashMap<>();
File file = new File(path);

if (file.isFile()) {
toolsRegistered.putAll(registerToolsFromFile(path));
} else if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
toolsRegistered.putAll(registerToolsFromFile(f.getPath()));
}
}
}
return toolsRegistered;
}

private static Map<String, Tool> registerToolsFromFile(String filePath) {
String fileName = Paths.get(filePath).getFileName().toString();
if (!fileName.endsWith(".java") || fileName.equals("setup.java")
|| fileName.startsWith("test")) {
return new HashMap<>();
}

Map<String, Tool> registeredTools = new HashMap<>();
try {
String code = Files.readString(Path.of(filePath));
Map<String, Map<String, Object>> toolSchemas =
ToolConvertor.convertCodeToToolSchemaAst(code);

for (Map.Entry<String, Map<String, Object>> entry : toolSchemas.entrySet()) {
String name = entry.getKey();
Map<String, Object> schemas = entry.getValue();
String toolCode = (String) schemas.remove("code");

INSTANCE.registerTool(name, filePath, schemas, "", toolCode,
null, null, null, false);
registeredTools.put(name, INSTANCE.getTool(name));
}
} catch (IOException e) {
log.error("Failed to read file: {}", filePath, e);
}
return registeredTools;
}
}
1 change: 1 addition & 0 deletions jcommon/pom.xml
Original file line number Diff line number Diff line change
@@ -90,6 +90,7 @@
<module>ai</module>
<module>ai/neo4j</module>
<module>hive</module>
<module>ai/m78</module>
</modules>

<!-- <distributionManagement>-->

0 comments on commit aef6761

Please sign in to comment.