From 8b15c8b2b2e266b140451efcbd4c3dc93e80b70c Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Sun, 7 Apr 2024 17:55:14 +0800 Subject: [PATCH 01/16] [ISSUE #4047] Support chatGPT source connector --- .../eventmesh-connector-chatgpt/build.gradle | 28 +++ .../gradle.properties | 18 ++ .../chatgpt/config/ChatGPTServerConfig.java | 32 +++ .../chatgpt/server/ChatGPTConnectServer.java | 40 ++++ .../source/config/ChatGPTSourceConfig.java | 30 +++ .../config/ChatGPTSourceConnectorConfig.java | 37 +++ .../connector/ChatGPTSourceConnector.java | 225 ++++++++++++++++++ .../src/main/resources/server-config.yml | 19 ++ .../src/main/resources/source-config.yml | 33 +++ .../connector/ChatGPTSourceConnectorTest.java | 115 +++++++++ .../src/test/resources/server-config.yml | 19 ++ .../src/test/resources/source-config.yml | 33 +++ settings.gradle | 1 + 13 files changed, 630 insertions(+) create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/gradle.properties create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/config/ChatGPTServerConfig.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/server/ChatGPTConnectServer.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/server-config.yml create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/server-config.yml create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle b/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle new file mode 100644 index 0000000000..d585347a76 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +dependencies { + api project(":eventmesh-openconnect:eventmesh-openconnect-java") + implementation project(":eventmesh-common") + implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0' + implementation 'io.vertx:vertx-web:4.4.6' + implementation 'com.theokanning.openai-gpt3-java:service:0.18.2' + + testImplementation "org.apache.httpcomponents:httpclient" + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' +} \ No newline at end of file diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/gradle.properties b/eventmesh-connectors/eventmesh-connector-chatgpt/gradle.properties new file mode 100644 index 0000000000..715bad3de4 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/gradle.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +pluginType=connector +pluginName=chatgpt \ No newline at end of file diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/config/ChatGPTServerConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/config/ChatGPTServerConfig.java new file mode 100644 index 0000000000..7d162920d7 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/config/ChatGPTServerConfig.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.config; + +import org.apache.eventmesh.openconnect.api.config.Config; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ChatGPTServerConfig extends Config { + + private boolean sourceEnable; + + private boolean sinkEnable; +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/server/ChatGPTConnectServer.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/server/ChatGPTConnectServer.java new file mode 100644 index 0000000000..ca104fe562 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/server/ChatGPTConnectServer.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.server; + +import org.apache.eventmesh.connector.chatgpt.config.ChatGPTServerConfig; +import org.apache.eventmesh.connector.chatgpt.source.connector.ChatGPTSourceConnector; +import org.apache.eventmesh.openconnect.Application; +import org.apache.eventmesh.openconnect.util.ConfigUtil; + +public class ChatGPTConnectServer { + + public static void main(String[] args) throws Exception { + ChatGPTServerConfig serverConfig = ConfigUtil.parse(ChatGPTServerConfig.class, "server-config.yml"); + + if (serverConfig.isSourceEnable()) { + Application chatGPTSourceApp = new Application(); + chatGPTSourceApp.run(ChatGPTSourceConnector.class); + } + + if (serverConfig.isSinkEnable()) { + // TODO support sink connector + } + } + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java new file mode 100644 index 0000000000..fac00904e4 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.config; + +import org.apache.eventmesh.openconnect.api.config.SourceConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ChatGPTSourceConfig extends SourceConfig { + + public ChatGPTSourceConnectorConfig connectorConfig; +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java new file mode 100644 index 0000000000..032adc45c5 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.config; + +import lombok.Data; + +@Data +public class ChatGPTSourceConnectorConfig { + + private String connectorName; + + private String path; + + private int port; + + private int idleTimeout; + + private String openaiToken; + + private String openaiModel; + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java new file mode 100644 index 0000000000..f3ea6be3e7 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.connector; + + +import org.apache.eventmesh.common.ThreadPoolFactory; +import org.apache.eventmesh.common.exception.EventMeshException; +import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; +import org.apache.eventmesh.openconnect.api.config.Config; +import org.apache.eventmesh.openconnect.api.connector.ConnectorContext; +import org.apache.eventmesh.openconnect.api.connector.SourceConnectorContext; +import org.apache.eventmesh.openconnect.api.source.Source; +import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; +import org.apache.eventmesh.openconnect.util.CloudEventUtil; + +import java.net.URI; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServer; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.ext.web.RequestBody; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.completion.chat.ChatMessageRole; +import com.theokanning.openai.service.OpenAiService; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ChatGPTSourceConnector implements Source { + + private static final int DEFAULT_BATCH_SIZE = 10; + + private ChatGPTSourceConfig sourceConfig; + private BlockingQueue queue; + private HttpServer server; + private OpenAiService openAiService; + private ExecutorService consumeExecutorService; + + @Data + private static class ChatGPTBody { + + String prompt; + + String source; + + String subject; + + @JsonProperty("datacontenttype") + String dataContentType; + + String type; + } + + @Override + public Class configClass() { + return ChatGPTSourceConfig.class; + } + + @Override + public void init(Config config) { + this.sourceConfig = (ChatGPTSourceConfig) config; + doInit(); + } + + @Override + public void init(ConnectorContext connectorContext) { + SourceConnectorContext sourceConnectorContext = (SourceConnectorContext) connectorContext; + this.sourceConfig = (ChatGPTSourceConfig) sourceConnectorContext.getSourceConfig(); + doInit(); + } + + private CloudEvent genGptConnectRecord(ChatGPTBody event) { + List chatMessages = new ArrayList<>(); + chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), event.getPrompt())); + ChatCompletionRequest req = + ChatCompletionRequest.builder().messages(chatMessages).model(sourceConfig.connectorConfig.getOpenaiModel()).build(); + StringBuilder gptData = new StringBuilder(); + + try { + openAiService.createChatCompletion(req).getChoices() + .forEach(chatCompletionChoice -> gptData.append(chatCompletionChoice.getMessage().getContent())); + } catch (Exception e) { + log.error("Failed to generate GPT connection record: {}", e.getMessage()); + } + + return CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create(event.getSource())) + .withType(event.getType()) + .withTime(ZonedDateTime.now().toOffsetDateTime()) + .withData(gptData.toString().getBytes()) + .withSubject(event.getSubject()) + .withDataContentType(event.getDataContentType()) + .build(); + } + + /** + * use proxy: + * ObjectMapper mapper = defaultObjectMapper(); + * Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890)); + * OkHttpClient client = + * OpenAiService.defaultClient(sourceConfig.connectorConfig.getOpenaiToken(), Duration.ofSeconds(60L)).newBuilder().proxy(proxy).build(); + * Retrofit retrofit = defaultRetrofit(client, mapper); + * OpenAiApi api = retrofit.create(OpenAiApi.class); + * this.openAiService = new OpenAiService(api); + */ + @SuppressWarnings("checkstyle:WhitespaceAround") + private void doInit() { + + this.openAiService = new OpenAiService(sourceConfig.connectorConfig.getOpenaiToken(), Duration.ofSeconds(60L)); + this.queue = new LinkedBlockingQueue<>(1024); + this.consumeExecutorService = ThreadPoolFactory.createThreadPoolExecutor( + Runtime.getRuntime().availableProcessors() * 2, + Runtime.getRuntime().availableProcessors() * 2, + "ChatGPTSourceThread"); + final Vertx vertx = Vertx.vertx(); + final Router router = Router.router(vertx); + router.route() + .path(this.sourceConfig.connectorConfig.getPath()).method(HttpMethod.POST) + .handler(BodyHandler.create()) + .handler(ctx -> { + try { + RequestBody body = ctx.body(); + ChatGPTBody bodyObject = body.asPojo(ChatGPTBody.class); + if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getPrompt() == null) { + throw new IllegalStateException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); + } + consumeExecutorService.execute(() -> { + try { + CloudEvent cloudEvent = genGptConnectRecord(bodyObject); + queue.add(cloudEvent); + log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); + ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); + } catch (Exception e) { + log.error("[ChatGPTSourceConnector]Error processing request: {}", e.getMessage(), e); + ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); + } + }); + } catch (Exception e) { + log.error("[ChatGPTSourceConnector] Malformed request. StatusCode={}", HttpResponseStatus.BAD_REQUEST.code(), e); + ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()).end(); + } + }); + this.server = vertx.createHttpServer(new HttpServerOptions().setPort(this.sourceConfig.connectorConfig.getPort()) + .setIdleTimeout(this.sourceConfig.connectorConfig.getIdleTimeout())).requestHandler(router); + } + + @Override + public void start() { + Throwable t = this.server.listen().cause(); + if (t != null) { + throw new EventMeshException("failed to start Vertx server", t); + } + } + + @Override + public void commit(ConnectRecord record) { + + } + + @Override + public String name() { + return this.sourceConfig.getConnectorConfig().getConnectorName(); + } + + @Override + public void stop() { + Throwable t = this.server.close().cause(); + if (t != null) { + throw new EventMeshException("failed to stop Vertx server", t); + } + } + + @Override + public List poll() { + List connectRecords = new ArrayList<>(DEFAULT_BATCH_SIZE); + for (int i = 0; i < DEFAULT_BATCH_SIZE; i++) { + try { + CloudEvent event = queue.poll(3, TimeUnit.SECONDS); + if (event == null) { + break; + } + connectRecords.add(CloudEventUtil.convertEventToRecord(event)); + } catch (InterruptedException e) { + break; + } + } + return connectRecords; + } + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/server-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/server-config.yml new file mode 100644 index 0000000000..0cd7b5b5ab --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/server-config.yml @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +sourceEnable: true +sinkEnable: false diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml new file mode 100644 index 0000000000..0241f66ec6 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +pubSubConfig: + meshAddress: 127.0.0.1:10000 + subject: TopicTest + idc: FT + env: PRD + group: chatgptSource + appId: 5032 + userName: chatgptSourceUser + passWord: chatgptPassWord +connectorConfig: + connectorName: chatgptSource + path: /chatgpt + port: 3756 + idleTimeout: 999 + openaiToken: + openaiModel: gpt-3.5-turbo diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java new file mode 100644 index 0000000000..40a71bcc4d --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.connector; + +import org.apache.eventmesh.common.utils.JsonUtils; +import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; +import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConnectorConfig; +import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; +import org.apache.eventmesh.openconnect.util.ConfigUtil; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ChatGPTSourceConnectorTest { + + private ChatGPTSourceConnector connector; + private ChatGPTSourceConnectorConfig config; + private CloseableHttpClient httpClient; + private String uri; + private final String expectedMessage = "Hello, can you tell me a story."; + + @BeforeEach + void setUp() throws Exception { + connector = new ChatGPTSourceConnector(); + ChatGPTSourceConfig sourceConfig = (ChatGPTSourceConfig) ConfigUtil.parse(connector.configClass()); + config = sourceConfig.getConnectorConfig(); + connector.init(sourceConfig); + connector.start(); + + uri = new URIBuilder().setScheme("http").setHost("127.0.0.1").setPort(config.getPort()).setPath(config.getPath()).build().toString(); + + httpClient = HttpClients.createDefault(); + } + + @Test + void testPoll() throws Exception { + final int batchSize = 10; + + for (int i = 0; i < batchSize; i++) { + HttpResponse resp = mockStructuredRequest(); + Assertions.assertEquals(resp.getStatusLine().getStatusCode(), HttpStatus.SC_OK); + } + + List res = connector.poll(); + Assertions.assertEquals(batchSize, res.size()); + + + // test invalid requests + HttpPost invalidPost = new HttpPost(uri); + TestEvent event = new TestEvent(); + event.type = "com.example.someevent"; + event.source = "/mycontext"; + event.datacontenttype = "text/plain"; + event.prompt = expectedMessage; + invalidPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); + HttpResponse resp = httpClient.execute(invalidPost); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, resp.getStatusLine().getStatusCode()); + } + + + HttpResponse mockStructuredRequest() throws Exception { + HttpPost httpPost = new HttpPost(uri); + TestEvent event = new TestEvent(); + event.type = "com.example.someevent"; + event.source = "/mycontext"; + event.subject = "test"; + event.datacontenttype = "text/plain"; + event.prompt = expectedMessage; + httpPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); + + return httpClient.execute(httpPost); + } + + @AfterEach + void tearDown() throws Exception { + connector.stop(); + httpClient.close(); + } + + class TestEvent { + + public String type; + public String source; + public String subject; + public String datacontenttype; + public String prompt; + } +} \ No newline at end of file diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/server-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/server-config.yml new file mode 100644 index 0000000000..0cd7b5b5ab --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/server-config.yml @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +sourceEnable: true +sinkEnable: false diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml new file mode 100644 index 0000000000..0241f66ec6 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +pubSubConfig: + meshAddress: 127.0.0.1:10000 + subject: TopicTest + idc: FT + env: PRD + group: chatgptSource + appId: 5032 + userName: chatgptSourceUser + passWord: chatgptPassWord +connectorConfig: + connectorName: chatgptSource + path: /chatgpt + port: 3756 + idleTimeout: 999 + openaiToken: + openaiModel: gpt-3.5-turbo diff --git a/settings.gradle b/settings.gradle index 645e6fb365..6a8e27bf98 100644 --- a/settings.gradle +++ b/settings.gradle @@ -77,6 +77,7 @@ include 'eventmesh-connectors:eventmesh-connector-wecom' include 'eventmesh-connectors:eventmesh-connector-slack' include 'eventmesh-connectors:eventmesh-connector-wechat' include 'eventmesh-connectors:eventmesh-connector-http' +include 'eventmesh-connectors:eventmesh-connector-chatgpt' include 'eventmesh-storage-plugin:eventmesh-storage-api' include 'eventmesh-storage-plugin:eventmesh-storage-standalone' From 317ffb47a7aeeae4af669977314b0dd36fb2d939 Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Mon, 8 Apr 2024 19:09:03 +0800 Subject: [PATCH 02/16] [ISSUE #4047] Add OpenAI configuration and adjust DTO --- .../source/config/ChatGPTSourceConfig.java | 5 + .../config/ChatGPTSourceConnectorConfig.java | 4 +- .../chatgpt/source/config/OpenaiConfig.java | 25 +++ .../source/config/OpenaiProxyConfig.java | 12 ++ .../connector/ChatGPTSourceConnector.java | 178 +++++++++++------- .../chatgpt/source/dto/ChatGPTRequestDTO.java | 26 +++ .../src/main/resources/prompt | 21 +++ .../src/main/resources/source-config.yml | 47 +++-- .../connector/ChatGPTSourceConnectorTest.java | 1 - .../src/test/resources/source-config.yml | 22 ++- 10 files changed, 251 insertions(+), 90 deletions(-) create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java index fac00904e4..9596866910 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConfig.java @@ -27,4 +27,9 @@ public class ChatGPTSourceConfig extends SourceConfig { public ChatGPTSourceConnectorConfig connectorConfig; + + public OpenaiProxyConfig openaiProxyConfig; + + public OpenaiConfig openaiConfig; + } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java index 032adc45c5..f958bc2acf 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -30,8 +30,6 @@ public class ChatGPTSourceConnectorConfig { private int idleTimeout; - private String openaiToken; - - private String openaiModel; + private boolean proxyEnable; } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java new file mode 100644 index 0000000000..1281b65714 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -0,0 +1,25 @@ +package org.apache.eventmesh.connector.chatgpt.source.config; + + +import java.util.List; +import java.util.Map; + +import lombok.Data; + +@Data +public class OpenaiConfig { + + private String token; + private String model; + private long timeout; + private Double temperature; + private Integer maxTokens; + private Boolean logprob; + private Double topLogprobs; + private Map logitBias; + private Double frequencyPenalty; + private Double presencePenalty; + private String user; + private List stop; + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java new file mode 100644 index 0000000000..9bfffc886d --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java @@ -0,0 +1,12 @@ +package org.apache.eventmesh.connector.chatgpt.source.config; + +import lombok.Data; + +@Data +public class OpenaiProxyConfig { + + private String host; + + private int port; + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index f3ea6be3e7..c28644065b 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -18,9 +18,20 @@ package org.apache.eventmesh.connector.chatgpt.source.connector; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; + +import static com.theokanning.openai.service.OpenAiService.defaultObjectMapper; +import static com.theokanning.openai.service.OpenAiService.defaultRetrofit; + import org.apache.eventmesh.common.ThreadPoolFactory; import org.apache.eventmesh.common.exception.EventMeshException; +import org.apache.eventmesh.common.utils.AssertUtils; +import org.apache.eventmesh.common.utils.JsonUtils; +import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiConfig; +import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiProxyConfig; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; +import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; import org.apache.eventmesh.openconnect.api.config.Config; import org.apache.eventmesh.openconnect.api.connector.ConnectorContext; import org.apache.eventmesh.openconnect.api.connector.SourceConnectorContext; @@ -28,6 +39,8 @@ import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; import org.apache.eventmesh.openconnect.util.CloudEventUtil; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URI; import java.time.Duration; import java.time.ZonedDateTime; @@ -50,13 +63,14 @@ import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.theokanning.openai.client.OpenAiApi; import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatCompletionRequest.ChatCompletionRequestBuilder; import com.theokanning.openai.completion.chat.ChatMessage; import com.theokanning.openai.completion.chat.ChatMessageRole; import com.theokanning.openai.service.OpenAiService; -import lombok.Data; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -68,22 +82,13 @@ public class ChatGPTSourceConnector implements Source { private BlockingQueue queue; private HttpServer server; private OpenAiService openAiService; - private ExecutorService consumeExecutorService; - - @Data - private static class ChatGPTBody { - - String prompt; - - String source; - - String subject; - - @JsonProperty("datacontenttype") - String dataContentType; + private final ExecutorService chatgptSourceExecutorService = + ThreadPoolFactory.createThreadPoolExecutor( + Runtime.getRuntime().availableProcessors() * 2, + Runtime.getRuntime().availableProcessors() * 2, + "ChatGPTSourceThread"); - String type; - } + private String chatCompletionRequestTemplateStr; @Override public Class configClass() { @@ -103,11 +108,68 @@ public void init(ConnectorContext connectorContext) { doInit(); } - private CloudEvent genGptConnectRecord(ChatGPTBody event) { + private void initOpenAi() { + OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); + AssertUtils.isTrue(openaiConfig.getTimeout() > 0, "openaiTimeout must be >= 0"); + boolean proxyEnable = sourceConfig.connectorConfig.isProxyEnable(); + if (proxyEnable) { + OpenaiProxyConfig chatgptProxyConfig = sourceConfig.openaiProxyConfig; + if (chatgptProxyConfig.getHost() == null) { + throw new IllegalStateException("chatgpt proxy config 'host' cannot be null"); + } + ObjectMapper mapper = defaultObjectMapper(); + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(chatgptProxyConfig.getHost(), chatgptProxyConfig.getPort())); + OkHttpClient client = OpenAiService + .defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())) + .newBuilder() + .proxy(proxy) + .build(); + Retrofit retrofit = defaultRetrofit(client, mapper); + OpenAiApi api = retrofit.create(OpenAiApi.class); + this.openAiService = new OpenAiService(api); + } else { + this.openAiService = + new OpenAiService(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())); + } + ChatCompletionRequestBuilder builder = ChatCompletionRequest + .builder() + .model(openaiConfig.getModel()); + AssertUtils.notNull(openaiConfig.getModel(), "model cannot be null"); + builder = builder.model(openaiConfig.getModel()); + if (openaiConfig.getUser() != null) { + builder = builder.user(openaiConfig.getUser()); + } + if (openaiConfig.getPresencePenalty() != null) { + builder = builder.presencePenalty(openaiConfig.getPresencePenalty()); + } + if (openaiConfig.getFrequencyPenalty() != null) { + builder = builder.frequencyPenalty(openaiConfig.getFrequencyPenalty()); + } + if (openaiConfig.getMaxTokens() != null) { + builder = builder.maxTokens(openaiConfig.getMaxTokens()); + } + if (openaiConfig.getTemperature() != null) { + builder = builder.temperature(openaiConfig.getTemperature()); + } + if (openaiConfig.getLogitBias() != null && !openaiConfig.getLogitBias().isEmpty()) { + builder = builder.logitBias(openaiConfig.getLogitBias()); + } + if (openaiConfig.getStop() != null && !openaiConfig.getStop().isEmpty()) { + builder = builder.stop(openaiConfig.getStop()); + } + this.chatCompletionRequestTemplateStr = JsonUtils.toJSONString(builder.build()); + } + + public ChatCompletionRequest newChatCompletionRequest(List chatMessages) { + ChatCompletionRequest request = JsonUtils.parseObject(chatCompletionRequestTemplateStr, ChatCompletionRequest.class); + request.setMessages(chatMessages); + return request; + } + + private CloudEvent genGptConnectRecord(ChatGPTRequestDTO event) { List chatMessages = new ArrayList<>(); chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), event.getPrompt())); - ChatCompletionRequest req = - ChatCompletionRequest.builder().messages(chatMessages).model(sourceConfig.connectorConfig.getOpenaiModel()).build(); + ChatCompletionRequest req = newChatCompletionRequest(chatMessages); StringBuilder gptData = new StringBuilder(); try { @@ -117,64 +179,40 @@ private CloudEvent genGptConnectRecord(ChatGPTBody event) { log.error("Failed to generate GPT connection record: {}", e.getMessage()); } - return CloudEventBuilder.v1() - .withId(UUID.randomUUID().toString()) - .withSource(URI.create(event.getSource())) - .withType(event.getType()) - .withTime(ZonedDateTime.now().toOffsetDateTime()) - .withData(gptData.toString().getBytes()) - .withSubject(event.getSubject()) - .withDataContentType(event.getDataContentType()) - .build(); + return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create(event.getSource())).withType(event.getType()) + .withTime(ZonedDateTime.now().toOffsetDateTime()).withData(gptData.toString().getBytes()).withSubject(event.getSubject()) + .withDataContentType(event.getDataContentType()).build(); } - /** - * use proxy: - * ObjectMapper mapper = defaultObjectMapper(); - * Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890)); - * OkHttpClient client = - * OpenAiService.defaultClient(sourceConfig.connectorConfig.getOpenaiToken(), Duration.ofSeconds(60L)).newBuilder().proxy(proxy).build(); - * Retrofit retrofit = defaultRetrofit(client, mapper); - * OpenAiApi api = retrofit.create(OpenAiApi.class); - * this.openAiService = new OpenAiService(api); - */ @SuppressWarnings("checkstyle:WhitespaceAround") private void doInit() { - - this.openAiService = new OpenAiService(sourceConfig.connectorConfig.getOpenaiToken(), Duration.ofSeconds(60L)); + initOpenAi(); this.queue = new LinkedBlockingQueue<>(1024); - this.consumeExecutorService = ThreadPoolFactory.createThreadPoolExecutor( - Runtime.getRuntime().availableProcessors() * 2, - Runtime.getRuntime().availableProcessors() * 2, - "ChatGPTSourceThread"); final Vertx vertx = Vertx.vertx(); final Router router = Router.router(vertx); - router.route() - .path(this.sourceConfig.connectorConfig.getPath()).method(HttpMethod.POST) - .handler(BodyHandler.create()) - .handler(ctx -> { - try { - RequestBody body = ctx.body(); - ChatGPTBody bodyObject = body.asPojo(ChatGPTBody.class); - if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getPrompt() == null) { - throw new IllegalStateException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); - } - consumeExecutorService.execute(() -> { - try { - CloudEvent cloudEvent = genGptConnectRecord(bodyObject); - queue.add(cloudEvent); - log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); - ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); - } catch (Exception e) { - log.error("[ChatGPTSourceConnector]Error processing request: {}", e.getMessage(), e); - ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); - } - }); - } catch (Exception e) { - log.error("[ChatGPTSourceConnector] Malformed request. StatusCode={}", HttpResponseStatus.BAD_REQUEST.code(), e); - ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()).end(); + router.route().path(this.sourceConfig.connectorConfig.getPath()).method(HttpMethod.POST).handler(BodyHandler.create()).handler(ctx -> { + try { + RequestBody body = ctx.body(); + ChatGPTRequestDTO bodyObject = body.asPojo(ChatGPTRequestDTO.class); + if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getPrompt() == null) { + throw new IllegalStateException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); } - }); + chatgptSourceExecutorService.execute(() -> { + try { + CloudEvent cloudEvent = genGptConnectRecord(bodyObject); + queue.add(cloudEvent); + log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); + ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); + } catch (Exception e) { + log.error("[ChatGPTSourceConnector] Error processing request: {}", e.getMessage(), e); + ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); + } + }); + } catch (Exception e) { + log.error("[ChatGPTSourceConnector] Malformed request. StatusCode={}", HttpResponseStatus.BAD_REQUEST.code(), e); + ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()).end(); + } + }); this.server = vertx.createHttpServer(new HttpServerOptions().setPort(this.sourceConfig.connectorConfig.getPort()) .setIdleTimeout(this.sourceConfig.connectorConfig.getIdleTimeout())).requestHandler(router); } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java new file mode 100644 index 0000000000..d99ebf1837 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -0,0 +1,26 @@ +package org.apache.eventmesh.connector.chatgpt.source.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChatGPTRequestDTO { + + private String source; + + private String subject; + + @JsonProperty("datacontenttype") + private String dataContentType; + + private String type; + + private String prompt; + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt new file mode 100644 index 0000000000..69a8852fb6 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt @@ -0,0 +1,21 @@ +You are an AI assistant named CloudEventsConverter. Your task is to convert input text provided by the user into a CloudEvents-formatted JSON object, avoid escape characters . + +For the following text, extract the following information: + +Create a CloudEvents-formatted JSON object with the following fields: +- specversion: Set to "1.0" (the current CloudEvents specification version) +- type: Set to \\\ {type} \\\ +- source: Set to \\\ {source} \\\ +- id: Set to \\\ {id} \\\ (Generate a unique identifier for the event (e.g., "A234-1234-1234")) +- time: Set to \\\ {time} \\\ (the current timestamp in ISO 8601 format. e.g:"2023-03-25T12:34:56.789Z") +- datacontenttype: Set to \\\ {datacontenttype} \\\ +- data: Set to the input text provided by the user +\\\ +{fields} +\\\ + +text: \\\ {text} \\\ + +If any of the fields marked as \\\ {} \\\ are null or empty, use a default value. + +Return the CloudEvents-formatted JSON object to the user,The format of the data field matches the datacontenttype,Just need to return the JSON object, nothing else needs to be returned。 diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml index 0241f66ec6..23c70bd32b 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml @@ -16,18 +16,37 @@ # pubSubConfig: - meshAddress: 127.0.0.1:10000 - subject: TopicTest - idc: FT - env: PRD - group: chatgptSource - appId: 5032 - userName: chatgptSourceUser - passWord: chatgptPassWord + meshAddress: 127.0.0.1:10000 + subject: TopicTest + idc: FT + env: PRD + group: chatgptSource + appId: 5032 + userName: chatgptSourceUser + passWord: chatgptPassWord connectorConfig: - connectorName: chatgptSource - path: /chatgpt - port: 3756 - idleTimeout: 999 - openaiToken: - openaiModel: gpt-3.5-turbo + connectorName: chatgptSource + path: /chatgpt + port: 3756 + idleTimeout: 999 + proxyEnable: true + +# https://platform.openai.com/docs/api-reference/chat/create +openaiConfig: + token: + model: gpt-3.5-turbo + timeout: 60 + temperature: 1 + maxTokens: + frequencyPenalty: 0 + presencePenalty: 0 + user: eventMesh + stop: [] + logitBias: { + + } + +openaiProxyConfig: + host: 127.0.0.1 + port: 7890 + diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index 40a71bcc4d..656e870357 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -71,7 +71,6 @@ void testPoll() throws Exception { List res = connector.poll(); Assertions.assertEquals(batchSize, res.size()); - // test invalid requests HttpPost invalidPost = new HttpPost(uri); TestEvent event = new TestEvent(); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml index 0241f66ec6..a3c0187515 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml @@ -29,5 +29,23 @@ connectorConfig: path: /chatgpt port: 3756 idleTimeout: 999 - openaiToken: - openaiModel: gpt-3.5-turbo + proxyEnable: true + +# https://platform.openai.com/docs/api-reference/chat/create +openaiConfig: + token: + model: gpt-3.5-turbo + timeout: 60 + temperature: 1 + maxTokens: + frequencyPenalty: 0 + presencePenalty: 0 + user: eventMesh + stop: [] + logitBias: { + + } + +openaiProxyConfig: + host: 127.0.0.1 + port: 7890 From 608d16904930b19f6240b44539e9b340fae780a0 Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Tue, 9 Apr 2024 00:14:43 +0800 Subject: [PATCH 03/16] [ISSUE #4047] Join parse request support --- .../config/ChatGPTSourceConnectorConfig.java | 2 + .../connector/ChatGPTSourceConnector.java | 141 +++++------------- .../chatgpt/source/dto/ChatGPTRequestDTO.java | 24 ++- .../source/enums/ChatGPTRequestType.java | 25 ++++ .../chatgpt/source/handlers/ChatHandler.java | 76 ++++++++++ .../chatgpt/source/handlers/ParseHandler.java | 43 ++++++ .../source/managers/OpenaiManager.java | 124 +++++++++++++++ .../src/main/resources/prompt | 18 +-- .../src/main/resources/source-config.yml | 1 + .../connector/ChatGPTSourceConnectorTest.java | 8 +- .../src/test/resources/source-config.yml | 1 + 11 files changed, 347 insertions(+), 116 deletions(-) create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/enums/ChatGPTRequestType.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java create mode 100644 eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java index f958bc2acf..8e1eba1410 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -32,4 +32,6 @@ public class ChatGPTSourceConnectorConfig { private boolean proxyEnable; + private String parsePromptFileName; + } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index c28644065b..96ffa5f802 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -17,21 +17,14 @@ package org.apache.eventmesh.connector.chatgpt.source.connector; - -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; - -import static com.theokanning.openai.service.OpenAiService.defaultObjectMapper; -import static com.theokanning.openai.service.OpenAiService.defaultRetrofit; - import org.apache.eventmesh.common.ThreadPoolFactory; import org.apache.eventmesh.common.exception.EventMeshException; import org.apache.eventmesh.common.utils.AssertUtils; -import org.apache.eventmesh.common.utils.JsonUtils; -import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiConfig; -import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiProxyConfig; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; +import org.apache.eventmesh.connector.chatgpt.source.handlers.ChatHandler; +import org.apache.eventmesh.connector.chatgpt.source.handlers.ParseHandler; +import org.apache.eventmesh.connector.chatgpt.source.managers.OpenaiManager; import org.apache.eventmesh.openconnect.api.config.Config; import org.apache.eventmesh.openconnect.api.connector.ConnectorContext; import org.apache.eventmesh.openconnect.api.connector.SourceConnectorContext; @@ -39,21 +32,20 @@ import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; import org.apache.eventmesh.openconnect.util.CloudEventUtil; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.URI; -import java.time.Duration; -import java.time.ZonedDateTime; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Vertx; import io.vertx.core.http.HttpMethod; @@ -63,14 +55,6 @@ import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.theokanning.openai.client.OpenAiApi; -import com.theokanning.openai.completion.chat.ChatCompletionRequest; -import com.theokanning.openai.completion.chat.ChatCompletionRequest.ChatCompletionRequestBuilder; -import com.theokanning.openai.completion.chat.ChatMessage; -import com.theokanning.openai.completion.chat.ChatMessageRole; -import com.theokanning.openai.service.OpenAiService; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -81,14 +65,16 @@ public class ChatGPTSourceConnector implements Source { private ChatGPTSourceConfig sourceConfig; private BlockingQueue queue; private HttpServer server; - private OpenAiService openAiService; private final ExecutorService chatgptSourceExecutorService = ThreadPoolFactory.createThreadPoolExecutor( Runtime.getRuntime().availableProcessors() * 2, Runtime.getRuntime().availableProcessors() * 2, "ChatGPTSourceThread"); - private String chatCompletionRequestTemplateStr; + private OpenaiManager openaiManager; + private String parsePromptTemplateStr; + private ChatHandler chatHandler; + private ParseHandler parseHandler; @Override public Class configClass() { @@ -108,85 +94,26 @@ public void init(ConnectorContext connectorContext) { doInit(); } - private void initOpenAi() { - OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); - AssertUtils.isTrue(openaiConfig.getTimeout() > 0, "openaiTimeout must be >= 0"); - boolean proxyEnable = sourceConfig.connectorConfig.isProxyEnable(); - if (proxyEnable) { - OpenaiProxyConfig chatgptProxyConfig = sourceConfig.openaiProxyConfig; - if (chatgptProxyConfig.getHost() == null) { - throw new IllegalStateException("chatgpt proxy config 'host' cannot be null"); - } - ObjectMapper mapper = defaultObjectMapper(); - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(chatgptProxyConfig.getHost(), chatgptProxyConfig.getPort())); - OkHttpClient client = OpenAiService - .defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())) - .newBuilder() - .proxy(proxy) - .build(); - Retrofit retrofit = defaultRetrofit(client, mapper); - OpenAiApi api = retrofit.create(OpenAiApi.class); - this.openAiService = new OpenAiService(api); - } else { - this.openAiService = - new OpenAiService(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())); - } - ChatCompletionRequestBuilder builder = ChatCompletionRequest - .builder() - .model(openaiConfig.getModel()); - AssertUtils.notNull(openaiConfig.getModel(), "model cannot be null"); - builder = builder.model(openaiConfig.getModel()); - if (openaiConfig.getUser() != null) { - builder = builder.user(openaiConfig.getUser()); - } - if (openaiConfig.getPresencePenalty() != null) { - builder = builder.presencePenalty(openaiConfig.getPresencePenalty()); - } - if (openaiConfig.getFrequencyPenalty() != null) { - builder = builder.frequencyPenalty(openaiConfig.getFrequencyPenalty()); - } - if (openaiConfig.getMaxTokens() != null) { - builder = builder.maxTokens(openaiConfig.getMaxTokens()); - } - if (openaiConfig.getTemperature() != null) { - builder = builder.temperature(openaiConfig.getTemperature()); - } - if (openaiConfig.getLogitBias() != null && !openaiConfig.getLogitBias().isEmpty()) { - builder = builder.logitBias(openaiConfig.getLogitBias()); - } - if (openaiConfig.getStop() != null && !openaiConfig.getStop().isEmpty()) { - builder = builder.stop(openaiConfig.getStop()); - } - this.chatCompletionRequestTemplateStr = JsonUtils.toJSONString(builder.build()); - } - - public ChatCompletionRequest newChatCompletionRequest(List chatMessages) { - ChatCompletionRequest request = JsonUtils.parseObject(chatCompletionRequestTemplateStr, ChatCompletionRequest.class); - request.setMessages(chatMessages); - return request; - } - - private CloudEvent genGptConnectRecord(ChatGPTRequestDTO event) { - List chatMessages = new ArrayList<>(); - chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), event.getPrompt())); - ChatCompletionRequest req = newChatCompletionRequest(chatMessages); - StringBuilder gptData = new StringBuilder(); - + public void initParsePrompt() { + String parsePromptFileName = sourceConfig.getConnectorConfig().getParsePromptFileName(); + URL resource = this.getClass().getClassLoader().getResource(parsePromptFileName); + AssertUtils.notNull(resource, String.format("cannot find file %s", parsePromptFileName)); try { - openAiService.createChatCompletion(req).getChoices() - .forEach(chatCompletionChoice -> gptData.append(chatCompletionChoice.getMessage().getContent())); - } catch (Exception e) { - log.error("Failed to generate GPT connection record: {}", e.getMessage()); + this.parsePromptTemplateStr = new String(Files.readAllBytes(Paths.get(resource.toURI()))); + } catch (URISyntaxException e) { + throw new IllegalStateException("The file path is invalid", e); + } catch (IOException e) { + throw new IllegalStateException("Unable to read file", e); } - - return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create(event.getSource())).withType(event.getType()) - .withTime(ZonedDateTime.now().toOffsetDateTime()).withData(gptData.toString().getBytes()).withSubject(event.getSubject()) - .withDataContentType(event.getDataContentType()).build(); } + @SuppressWarnings("checkstyle:WhitespaceAround") private void doInit() { - initOpenAi(); + initParsePrompt(); + this.openaiManager = new OpenaiManager(sourceConfig); + this.chatHandler = new ChatHandler(this.openaiManager); + this.parseHandler = new ParseHandler(openaiManager, parsePromptTemplateStr); this.queue = new LinkedBlockingQueue<>(1024); final Vertx vertx = Vertx.vertx(); final Router router = Router.router(vertx); @@ -194,12 +121,22 @@ private void doInit() { try { RequestBody body = ctx.body(); ChatGPTRequestDTO bodyObject = body.asPojo(ChatGPTRequestDTO.class); - if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getPrompt() == null) { + if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getText() == null) { throw new IllegalStateException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); } chatgptSourceExecutorService.execute(() -> { try { - CloudEvent cloudEvent = genGptConnectRecord(bodyObject); + CloudEvent cloudEvent; + switch (bodyObject.getRequestType()) { + case CHAT: + cloudEvent = chatHandler.invoke(bodyObject); + break; + case PARSE: + cloudEvent = parseHandler.invoke(bodyObject); + break; + default: + throw new IllegalStateException("the request type is illegal"); + } queue.add(cloudEvent); log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java index d99ebf1837..83ae2900c0 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -1,10 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.eventmesh.connector.chatgpt.source.dto; +import org.apache.eventmesh.connector.chatgpt.source.enums.ChatGPTRequestType; + import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @Data @@ -12,6 +30,8 @@ @NoArgsConstructor public class ChatGPTRequestDTO { + private ChatGPTRequestType requestType; + private String source; private String subject; @@ -21,6 +41,6 @@ public class ChatGPTRequestDTO { private String type; - private String prompt; + private String text; } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/enums/ChatGPTRequestType.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/enums/ChatGPTRequestType.java new file mode 100644 index 0000000000..9930525651 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/enums/ChatGPTRequestType.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.enums; + + +public enum ChatGPTRequestType { + + CHAT, PARSE; + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java new file mode 100644 index 0000000000..924729b6c4 --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.handlers; + + +import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; +import org.apache.eventmesh.connector.chatgpt.source.managers.OpenaiManager; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; + +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.completion.chat.ChatMessageRole; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ChatHandler { + + private final OpenaiManager openaiManager; + + public ChatHandler(OpenaiManager openaiManager) { + this.openaiManager = openaiManager; + } + + public CloudEvent invoke(ChatGPTRequestDTO event) { + return genGptConnectRecord(event); + } + + private CloudEvent genGptConnectRecord(ChatGPTRequestDTO event) { + List chatMessages = new ArrayList<>(); + chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), event.getText())); + ChatCompletionRequest req = openaiManager.newChatCompletionRequest(chatMessages); + StringBuilder gptData = new StringBuilder(); + + try { + openaiManager.getOpenAiService().createChatCompletion(req).getChoices() + .forEach(chatCompletionChoice -> gptData.append(chatCompletionChoice.getMessage().getContent())); + } catch (Exception e) { + log.error("Failed to generate GPT connection record: {}", e.getMessage()); + } + + return CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create(event.getSource())) + .withType(event.getType()) + .withTime(ZonedDateTime.now().toOffsetDateTime()) + .withData(gptData.toString().getBytes()) + .withSubject(event.getSubject()) + .withDataContentType(event.getDataContentType()) + .build(); + } + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java new file mode 100644 index 0000000000..599a5b715f --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.handlers; + + +import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; +import org.apache.eventmesh.connector.chatgpt.source.managers.OpenaiManager; + +import io.cloudevents.CloudEvent; + +public class ParseHandler { + + private final OpenaiManager openaiManager; + + private final String promptTemplate; + + public ParseHandler(OpenaiManager openaiManager, String promptTemplate) { + this.openaiManager = openaiManager; + this.promptTemplate = promptTemplate; + } + + public CloudEvent invoke(ChatGPTRequestDTO event) { + // todo use StringSubstitutor event and promptTemplate translate to final prompt + + return null; + } + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java new file mode 100644 index 0000000000..9e97e6cdbc --- /dev/null +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.connector.chatgpt.source.managers; + +import static com.theokanning.openai.service.OpenAiService.defaultObjectMapper; +import static com.theokanning.openai.service.OpenAiService.defaultRetrofit; + +import org.apache.eventmesh.common.utils.AssertUtils; +import org.apache.eventmesh.common.utils.JsonUtils; +import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; +import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiConfig; +import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiProxyConfig; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.time.Duration; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.theokanning.openai.client.OpenAiApi; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatCompletionRequest.ChatCompletionRequestBuilder; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.service.OpenAiService; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; + + +@Slf4j +public class OpenaiManager { + + @Getter + private OpenAiService openAiService; + + private String chatCompletionRequestTemplateStr; + + public OpenaiManager(ChatGPTSourceConfig sourceConfig) { + initOpenAi(sourceConfig); + } + + public String getResult(ChatCompletionRequest req) { + StringBuilder gptData = new StringBuilder(); + try { + openAiService.createChatCompletion(req).getChoices() + .forEach(chatCompletionChoice -> gptData.append(chatCompletionChoice.getMessage().getContent())); + } catch (Exception e) { + log.error("Failed to generate GPT connection record: {}", e.getMessage()); + } + return gptData.toString(); + } + + public ChatCompletionRequest newChatCompletionRequest(List chatMessages) { + ChatCompletionRequest request = JsonUtils.parseObject(chatCompletionRequestTemplateStr, ChatCompletionRequest.class); + request.setMessages(chatMessages); + return request; + } + + private void initOpenAi(ChatGPTSourceConfig sourceConfig) { + OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); + AssertUtils.isTrue(openaiConfig.getTimeout() > 0, "openaiTimeout must be >= 0"); + boolean proxyEnable = sourceConfig.connectorConfig.isProxyEnable(); + if (proxyEnable) { + OpenaiProxyConfig chatgptProxyConfig = sourceConfig.openaiProxyConfig; + if (chatgptProxyConfig.getHost() == null) { + throw new IllegalStateException("chatgpt proxy config 'host' cannot be null"); + } + ObjectMapper mapper = defaultObjectMapper(); + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(chatgptProxyConfig.getHost(), chatgptProxyConfig.getPort())); + OkHttpClient client = + OpenAiService.defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())).newBuilder().proxy(proxy).build(); + Retrofit retrofit = defaultRetrofit(client, mapper); + OpenAiApi api = retrofit.create(OpenAiApi.class); + this.openAiService = new OpenAiService(api); + } else { + this.openAiService = new OpenAiService(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())); + } + ChatCompletionRequestBuilder builder = ChatCompletionRequest.builder().model(openaiConfig.getModel()); + AssertUtils.notNull(openaiConfig.getModel(), "model cannot be null"); + builder = builder.model(openaiConfig.getModel()); + if (openaiConfig.getUser() != null) { + builder = builder.user(openaiConfig.getUser()); + } + if (openaiConfig.getPresencePenalty() != null) { + builder = builder.presencePenalty(openaiConfig.getPresencePenalty()); + } + if (openaiConfig.getFrequencyPenalty() != null) { + builder = builder.frequencyPenalty(openaiConfig.getFrequencyPenalty()); + } + if (openaiConfig.getMaxTokens() != null) { + builder = builder.maxTokens(openaiConfig.getMaxTokens()); + } + if (openaiConfig.getTemperature() != null) { + builder = builder.temperature(openaiConfig.getTemperature()); + } + if (openaiConfig.getLogitBias() != null && !openaiConfig.getLogitBias().isEmpty()) { + builder = builder.logitBias(openaiConfig.getLogitBias()); + } + if (openaiConfig.getStop() != null && !openaiConfig.getStop().isEmpty()) { + builder = builder.stop(openaiConfig.getStop()); + } + this.chatCompletionRequestTemplateStr = JsonUtils.toJSONString(builder.build()); + } + + +} diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt index 69a8852fb6..661bcb315a 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt @@ -4,17 +4,17 @@ For the following text, extract the following information: Create a CloudEvents-formatted JSON object with the following fields: - specversion: Set to "1.0" (the current CloudEvents specification version) -- type: Set to \\\ {type} \\\ -- source: Set to \\\ {source} \\\ -- id: Set to \\\ {id} \\\ (Generate a unique identifier for the event (e.g., "A234-1234-1234")) -- time: Set to \\\ {time} \\\ (the current timestamp in ISO 8601 format. e.g:"2023-03-25T12:34:56.789Z") -- datacontenttype: Set to \\\ {datacontenttype} \\\ +- type: Set to \\\ ${type} \\\ +- source: Set to \\\ ${source} \\\ +- id: Set to \\\ ${id} \\\ (Generate a unique identifier for the event.) +- time: Set to \\\ ${time} \\\ +- datacontenttype: Set to \\\ ${datacontenttype} \\\ (e.g.,application/json) - data: Set to the input text provided by the user -\\\ -{fields} -\\\ + \\\ + ${fields} + \\\ -text: \\\ {text} \\\ +text: \\\ ${text} \\\ If any of the fields marked as \\\ {} \\\ are null or empty, use a default value. diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml index 23c70bd32b..28476ee85a 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml @@ -30,6 +30,7 @@ connectorConfig: port: 3756 idleTimeout: 999 proxyEnable: true + parsePromptFileName: prompt # https://platform.openai.com/docs/api-reference/chat/create openaiConfig: diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index 656e870357..67c31984aa 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -77,7 +77,7 @@ void testPoll() throws Exception { event.type = "com.example.someevent"; event.source = "/mycontext"; event.datacontenttype = "text/plain"; - event.prompt = expectedMessage; + event.text = expectedMessage; invalidPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); HttpResponse resp = httpClient.execute(invalidPost); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, resp.getStatusLine().getStatusCode()); @@ -91,7 +91,8 @@ HttpResponse mockStructuredRequest() throws Exception { event.source = "/mycontext"; event.subject = "test"; event.datacontenttype = "text/plain"; - event.prompt = expectedMessage; + event.text = expectedMessage; + event.requestType = "CHAT"; httpPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); return httpClient.execute(httpPost); @@ -105,10 +106,11 @@ void tearDown() throws Exception { class TestEvent { + public String requestType; public String type; public String source; public String subject; public String datacontenttype; - public String prompt; + public String text; } } \ No newline at end of file diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml index a3c0187515..ee4fd9dee8 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml @@ -30,6 +30,7 @@ connectorConfig: port: 3756 idleTimeout: 999 proxyEnable: true + parsePromptFileName: prompt # https://platform.openai.com/docs/api-reference/chat/create openaiConfig: From e2dcf441eae07ee1d5d19e83c8ed33b0eb25819f Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Wed, 10 Apr 2024 15:35:39 +0800 Subject: [PATCH 04/16] [ISSUE #4047] impl Parse request --- .../connector/ChatGPTSourceConnector.java | 75 +++++++++----- .../chatgpt/source/dto/ChatGPTRequestDTO.java | 18 +++- .../chatgpt/source/handlers/ChatHandler.java | 11 +-- .../chatgpt/source/handlers/ParseHandler.java | 98 ++++++++++++++++++- .../src/main/resources/prompt | 42 ++++---- .../connector/ChatGPTSourceConnectorTest.java | 31 +++++- 6 files changed, 217 insertions(+), 58 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index 96ffa5f802..359aa08888 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -22,6 +22,7 @@ import org.apache.eventmesh.common.utils.AssertUtils; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; +import org.apache.eventmesh.connector.chatgpt.source.enums.ChatGPTRequestType; import org.apache.eventmesh.connector.chatgpt.source.handlers.ChatHandler; import org.apache.eventmesh.connector.chatgpt.source.handlers.ParseHandler; import org.apache.eventmesh.connector.chatgpt.source.managers.OpenaiManager; @@ -53,6 +54,7 @@ import io.vertx.core.http.HttpServerOptions; import io.vertx.ext.web.RequestBody; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import lombok.extern.slf4j.Slf4j; @@ -121,39 +123,60 @@ private void doInit() { try { RequestBody body = ctx.body(); ChatGPTRequestDTO bodyObject = body.asPojo(ChatGPTRequestDTO.class); - if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getText() == null) { - throw new IllegalStateException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); - } - chatgptSourceExecutorService.execute(() -> { - try { - CloudEvent cloudEvent; - switch (bodyObject.getRequestType()) { - case CHAT: - cloudEvent = chatHandler.invoke(bodyObject); - break; - case PARSE: - cloudEvent = parseHandler.invoke(bodyObject); - break; - default: - throw new IllegalStateException("the request type is illegal"); - } - queue.add(cloudEvent); - log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); - ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); - } catch (Exception e) { - log.error("[ChatGPTSourceConnector] Error processing request: {}", e.getMessage(), e); - ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); - } - }); + validateRequestDTO(bodyObject); + handleRequest(bodyObject, ctx); } catch (Exception e) { - log.error("[ChatGPTSourceConnector] Malformed request. StatusCode={}", HttpResponseStatus.BAD_REQUEST.code(), e); - ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()).end(); + handleError(e, ctx); } }); this.server = vertx.createHttpServer(new HttpServerOptions().setPort(this.sourceConfig.connectorConfig.getPort()) .setIdleTimeout(this.sourceConfig.connectorConfig.getIdleTimeout())).requestHandler(router); } + + private void validateRequestDTO(ChatGPTRequestDTO bodyObject) { + if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getText() == null) { + throw new IllegalArgumentException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); + } + } + + private void handleRequest(ChatGPTRequestDTO bodyObject, RoutingContext ctx) { + chatgptSourceExecutorService.execute(() -> { + try { + ChatGPTRequestType chatgptRequestType = ChatGPTRequestType.valueOf(bodyObject.getRequestType()); + CloudEvent cloudEvent = invokeHandler(chatgptRequestType, bodyObject); + queue.add(cloudEvent); + log.info("[ChatGPTSourceConnector] Succeed to convert payload into CloudEvent."); + ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); + } catch (IllegalArgumentException e) { + log.error("[ChatGPTSourceConnector] the request type is illegal: {}", e.getMessage(), e); + ctx.response() + .setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) + .setStatusMessage(String.format("request type '%s' is not supported", bodyObject.getRequestType())) + .end(); + } catch (Exception e) { + log.error("[ChatGPTSourceConnector] Error processing request: {}", e.getMessage(), e); + ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); + } + }); + } + + private CloudEvent invokeHandler(ChatGPTRequestType chatgptRequestType, ChatGPTRequestDTO bodyObject) { + switch (chatgptRequestType) { + case CHAT: + return chatHandler.invoke(bodyObject); + case PARSE: + return parseHandler.invoke(bodyObject); + default: + throw new IllegalStateException("the request type is illegal"); + } + } + + private void handleError(Exception e, RoutingContext ctx) { + log.error("[ChatGPTSourceConnector] Malformed request.", e); + ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()).end(); + } + @Override public void start() { Throwable t = this.server.listen().cause(); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java index 83ae2900c0..3e339011d0 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -19,6 +19,11 @@ import org.apache.eventmesh.connector.chatgpt.source.enums.ChatGPTRequestType; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -30,7 +35,7 @@ @NoArgsConstructor public class ChatGPTRequestDTO { - private ChatGPTRequestType requestType; + private String requestType = ChatGPTRequestType.CHAT.name(); private String source; @@ -43,4 +48,15 @@ public class ChatGPTRequestDTO { private String text; + private String fields; + + @JsonInclude + private String id = UUID.randomUUID().toString(); + + @JsonInclude + private String time = ZonedDateTime.now().toOffsetDateTime().toString(); + + public String getFields() { + return fields.replace(";", "\n"); + } } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java index 924729b6c4..6d79a0559f 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ChatHandler.java @@ -53,21 +53,14 @@ private CloudEvent genGptConnectRecord(ChatGPTRequestDTO event) { List chatMessages = new ArrayList<>(); chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), event.getText())); ChatCompletionRequest req = openaiManager.newChatCompletionRequest(chatMessages); - StringBuilder gptData = new StringBuilder(); - - try { - openaiManager.getOpenAiService().createChatCompletion(req).getChoices() - .forEach(chatCompletionChoice -> gptData.append(chatCompletionChoice.getMessage().getContent())); - } catch (Exception e) { - log.error("Failed to generate GPT connection record: {}", e.getMessage()); - } + String chatResult = openaiManager.getResult(req); return CloudEventBuilder.v1() .withId(UUID.randomUUID().toString()) .withSource(URI.create(event.getSource())) .withType(event.getType()) .withTime(ZonedDateTime.now().toOffsetDateTime()) - .withData(gptData.toString().getBytes()) + .withData(chatResult.getBytes()) .withSubject(event.getSubject()) .withDataContentType(event.getDataContentType()) .build(); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java index 599a5b715f..008fc140e2 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java @@ -18,26 +18,120 @@ package org.apache.eventmesh.connector.chatgpt.source.handlers; +import org.apache.eventmesh.common.Constants; import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; import org.apache.eventmesh.connector.chatgpt.source.managers.OpenaiManager; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import io.cloudevents.CloudEvent; +import io.cloudevents.jackson.JsonFormat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.completion.chat.ChatMessageRole; + +import lombok.extern.slf4j.Slf4j; +@Slf4j public class ParseHandler { private final OpenaiManager openaiManager; private final String promptTemplate; + private static final JsonFormat jsonFormat = new JsonFormat(false, true); + + public ParseHandler(OpenaiManager openaiManager, String promptTemplate) { this.openaiManager = openaiManager; this.promptTemplate = promptTemplate; } + @SuppressWarnings("checkstyle:WhitespaceAfter") public CloudEvent invoke(ChatGPTRequestDTO event) { - // todo use StringSubstitutor event and promptTemplate translate to final prompt + Map map = convertToMap(event); + + StringSubstitutor substitute = new StringSubstitutor(map); + String finalPrompt = substitute.replace(promptTemplate); + List chatMessages = new ArrayList<>(); + chatMessages.add(new ChatMessage(ChatMessageRole.USER.value(), finalPrompt)); + ChatCompletionRequest req = openaiManager.newChatCompletionRequest(chatMessages); + String chatResult = openaiManager.getResult(req); + chatResult = StringUtils.removeFirst(chatResult, "```json"); + chatResult = StringUtils.removeEnd(chatResult, "```"); + CloudEvent cloudEvent; + try { + cloudEvent = jsonFormat.deserialize(chatResult.getBytes(Constants.DEFAULT_CHARSET)); + } catch (Exception e) { + throw new IllegalStateException("cloudEvent parse fail, please check your parse prompt file content", e); + } + return cloudEvent; + } + + public Map convertToMap(Object obj) { + Map map = new HashMap<>(); + Class clazz = obj.getClass(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (field.isSynthetic()) { + continue; + } + if (Map.class.isAssignableFrom(field.getType()) || List.class.isAssignableFrom(field.getType())) { + continue; + } + try { + String key = field.getName(); + if (field.isAnnotationPresent(JsonProperty.class)) { + JsonProperty annotation = field.getAnnotation(JsonProperty.class); + key = annotation.value(); + } + Method getter = getGetter(field, clazz); + map.put(key, String.valueOf(getter.invoke(obj))); + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new IllegalStateException("convert to Map is fail", e); + } + } + + return map; + } + + public Method getGetter(Field field, Class clazz) throws NoSuchMethodException { + boolean isBooleanField = false; + if (boolean.class.isAssignableFrom(field.getType()) || Boolean.class.isAssignableFrom(field.getType())) { + isBooleanField = true; + } + String handledFieldName = upperFirst(field.getName()); + String methodName; + if (isBooleanField) { + methodName = "is" + handledFieldName; + } else { + methodName = "get" + handledFieldName; + } + return clazz.getDeclaredMethod(methodName); + } - return null; + public String upperFirst(String str) { + if (null == str) { + return null; + } + if (!str.isEmpty()) { + char firstChar = str.charAt(0); + if (Character.isLowerCase(firstChar)) { + return Character.toUpperCase(firstChar) + StringUtils.substring(str, 1); + } + } + return str; } } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt index 661bcb315a..b598c8e973 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt @@ -1,21 +1,27 @@ -You are an AI assistant named CloudEventsConverter. Your task is to convert input text provided by the user into a CloudEvents-formatted JSON object, avoid escape characters . +You are an AI assistant named CloudEventsConverter. avoid escape characters . +Your task is to construct a JSON object in CloudEvents format. Based on the field name and field description in the 'data' field of the CloudEvents formatted JSON object, convert the input text provided by the user into the content of the 'data' field, which must comply with the specifications of the content of the 'datacontenttype' field. +The role is : + - If the 'datacontenttype' field content is 'application/json', then the' data 'field content should be a JSON object, + - else If the 'datacontenttype' field content is not 'application/json' and is 'application/xml', then the' data 'field content should be a string in XML format and the outermost of XML format is , inside is the XML generated by you based on field info; + - else the 'datacontenttype' field content is not 'application/json' and 'application/xml', then the' data 'field content is string of the 'text' field content; +Except for the content of the data field, all other values should be set to and cannot be modified. Finally, return to me the JSON object in CloudEvents format that you constructed -For the following text, extract the following information: +The following text is the field name and field description in the 'data' field of the CloudEvents-formatted JSON object, extract the following information: + +${fields} + -Create a CloudEvents-formatted JSON object with the following fields: -- specversion: Set to "1.0" (the current CloudEvents specification version) -- type: Set to \\\ ${type} \\\ -- source: Set to \\\ ${source} \\\ -- id: Set to \\\ ${id} \\\ (Generate a unique identifier for the event.) -- time: Set to \\\ ${time} \\\ -- datacontenttype: Set to \\\ ${datacontenttype} \\\ (e.g.,application/json) -- data: Set to the input text provided by the user - \\\ - ${fields} - \\\ +text: ${text} -text: \\\ ${text} \\\ - -If any of the fields marked as \\\ {} \\\ are null or empty, use a default value. - -Return the CloudEvents-formatted JSON object to the user,The format of the data field matches the datacontenttype,Just need to return the JSON object, nothing else needs to be returned。 +The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```": +```json +{ + "specversion": string, Set to "1.0" + "type": string, Set to ${type} + "source": string, Set to ${source} + "subject": string, Set to ${subject} + "id": string, Set to ${id} + "time": string, Set to ${time} + "datacontenttype": string, Set to ${datacontenttype} + "data": object or string +} \ No newline at end of file diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index 67c31984aa..047b915ac3 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -46,6 +46,8 @@ class ChatGPTSourceConnectorTest { private String uri; private final String expectedMessage = "Hello, can you tell me a story."; + private final String expectedParseMessage = "User 13356288979 from Tianjin store placed an order with order number 11221122"; + @BeforeEach void setUp() throws Exception { connector = new ChatGPTSourceConnector(); @@ -64,13 +66,21 @@ void testPoll() throws Exception { final int batchSize = 10; for (int i = 0; i < batchSize; i++) { - HttpResponse resp = mockStructuredRequest(); + HttpResponse resp = mockStructuredChatRequest(); Assertions.assertEquals(resp.getStatusLine().getStatusCode(), HttpStatus.SC_OK); } List res = connector.poll(); Assertions.assertEquals(batchSize, res.size()); + for (int i = 0; i < batchSize; i++) { + HttpResponse resp = mockStructuredParseRequest(); + Assertions.assertEquals(resp.getStatusLine().getStatusCode(), HttpStatus.SC_OK); + } + + List res1 = connector.poll(); + Assertions.assertEquals(batchSize, res1.size()); + // test invalid requests HttpPost invalidPost = new HttpPost(uri); TestEvent event = new TestEvent(); @@ -84,7 +94,7 @@ void testPoll() throws Exception { } - HttpResponse mockStructuredRequest() throws Exception { + HttpResponse mockStructuredChatRequest() throws Exception { HttpPost httpPost = new HttpPost(uri); TestEvent event = new TestEvent(); event.type = "com.example.someevent"; @@ -98,6 +108,22 @@ HttpResponse mockStructuredRequest() throws Exception { return httpClient.execute(httpPost); } + + HttpResponse mockStructuredParseRequest() throws Exception { + HttpPost httpPost = new HttpPost(uri); + TestEvent event = new TestEvent(); + event.type = "com.example.someevent"; + event.source = "/mycontext"; + event.subject = "test"; + event.datacontenttype = "application/json"; + event.text = expectedParseMessage; + event.requestType = "PARSE1"; + event.fields = "orderNo:this is order number;address:this is a address;phone:this is phone number"; + httpPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); + + return httpClient.execute(httpPost); + } + @AfterEach void tearDown() throws Exception { connector.stop(); @@ -112,5 +138,6 @@ class TestEvent { public String subject; public String datacontenttype; public String text; + public String fields; } } \ No newline at end of file From ca42dedd15307c38b73a79ae5e0c48d1ade1a8ec Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Wed, 10 Apr 2024 15:47:58 +0800 Subject: [PATCH 05/16] [ISSUE #4047] fix code style --- .../chatgpt/source/config/OpenaiConfig.java | 17 +++++++++++++++++ .../source/config/OpenaiProxyConfig.java | 17 +++++++++++++++++ .../chatgpt/source/dto/ChatGPTRequestDTO.java | 1 - 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java index 1281b65714..1954c74ddd 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.eventmesh.connector.chatgpt.source.config; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java index 9bfffc886d..14dd69f350 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiProxyConfig.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.eventmesh.connector.chatgpt.source.config; import lombok.Data; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java index 3e339011d0..07cac5ca42 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -20,7 +20,6 @@ import org.apache.eventmesh.connector.chatgpt.source.enums.ChatGPTRequestType; import java.time.ZonedDateTime; -import java.util.List; import java.util.UUID; import com.fasterxml.jackson.annotation.JsonInclude; From 333199762ef144e238f4e570a044fbe022587e3a Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Wed, 10 Apr 2024 17:43:23 +0800 Subject: [PATCH 06/16] [ISSUE #4047] fix code style --- .../connector/ChatGPTSourceConnector.java | 36 ++++++++++--------- .../chatgpt/source/handlers/ParseHandler.java | 5 +-- .../src/main/resources/prompt | 17 +++++++++ .../connector/ChatGPTSourceConnectorTest.java | 9 +++-- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index 359aa08888..dc34f855cd 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -33,12 +33,13 @@ import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; import org.apache.eventmesh.openconnect.util.CloudEventUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.FileReader; import java.io.IOException; -import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -67,11 +68,8 @@ public class ChatGPTSourceConnector implements Source { private ChatGPTSourceConfig sourceConfig; private BlockingQueue queue; private HttpServer server; - private final ExecutorService chatgptSourceExecutorService = - ThreadPoolFactory.createThreadPoolExecutor( - Runtime.getRuntime().availableProcessors() * 2, - Runtime.getRuntime().availableProcessors() * 2, - "ChatGPTSourceThread"); + private final ExecutorService chatgptSourceExecutorService = ThreadPoolFactory.createThreadPoolExecutor( + Runtime.getRuntime().availableProcessors() * 2, Runtime.getRuntime().availableProcessors() * 2, "ChatGPTSourceThread"); private OpenaiManager openaiManager; private String parsePromptTemplateStr; @@ -98,12 +96,18 @@ public void init(ConnectorContext connectorContext) { public void initParsePrompt() { String parsePromptFileName = sourceConfig.getConnectorConfig().getParsePromptFileName(); - URL resource = this.getClass().getClassLoader().getResource(parsePromptFileName); + URL resource = Thread.currentThread().getContextClassLoader().getResource(parsePromptFileName); AssertUtils.notNull(resource, String.format("cannot find file %s", parsePromptFileName)); - try { - this.parsePromptTemplateStr = new String(Files.readAllBytes(Paths.get(resource.toURI()))); - } catch (URISyntaxException e) { - throw new IllegalStateException("The file path is invalid", e); + String filePath = resource.getPath(); + try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { + StringBuilder builder = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + if (!line.startsWith("#") && StringUtils.isNotBlank(line)) { + builder.append(line).append("\n"); + } + } + this.parsePromptTemplateStr = builder.toString(); } catch (IOException e) { throw new IllegalStateException("Unable to read file", e); } @@ -150,10 +154,8 @@ private void handleRequest(ChatGPTRequestDTO bodyObject, RoutingContext ctx) { ctx.response().setStatusCode(HttpResponseStatus.OK.code()).end(); } catch (IllegalArgumentException e) { log.error("[ChatGPTSourceConnector] the request type is illegal: {}", e.getMessage(), e); - ctx.response() - .setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) - .setStatusMessage(String.format("request type '%s' is not supported", bodyObject.getRequestType())) - .end(); + ctx.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) + .setStatusMessage(String.format("request type '%s' is not supported", bodyObject.getRequestType())).end(); } catch (Exception e) { log.error("[ChatGPTSourceConnector] Error processing request: {}", e.getMessage(), e); ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java index 008fc140e2..aad3d384cf 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/handlers/ParseHandler.java @@ -107,10 +107,7 @@ public Map convertToMap(Object obj) { } public Method getGetter(Field field, Class clazz) throws NoSuchMethodException { - boolean isBooleanField = false; - if (boolean.class.isAssignableFrom(field.getType()) || Boolean.class.isAssignableFrom(field.getType())) { - isBooleanField = true; - } + boolean isBooleanField = boolean.class.isAssignableFrom(field.getType()) || Boolean.class.isAssignableFrom(field.getType()); String handledFieldName = upperFirst(field.getName()); String methodName; if (isBooleanField) { diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt index b598c8e973..e10ecc331d 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/prompt @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + You are an AI assistant named CloudEventsConverter. avoid escape characters . Your task is to construct a JSON object in CloudEvents format. Based on the field name and field description in the 'data' field of the CloudEvents formatted JSON object, convert the input text provided by the user into the content of the 'data' field, which must comply with the specifications of the content of the 'datacontenttype' field. The role is : diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index 047b915ac3..a9fb17c450 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -82,12 +82,12 @@ void testPoll() throws Exception { Assertions.assertEquals(batchSize, res1.size()); // test invalid requests - HttpPost invalidPost = new HttpPost(uri); TestEvent event = new TestEvent(); event.type = "com.example.someevent"; event.source = "/mycontext"; event.datacontenttype = "text/plain"; event.text = expectedMessage; + HttpPost invalidPost = new HttpPost(uri); invalidPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); HttpResponse resp = httpClient.execute(invalidPost); Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, resp.getStatusLine().getStatusCode()); @@ -95,7 +95,6 @@ void testPoll() throws Exception { HttpResponse mockStructuredChatRequest() throws Exception { - HttpPost httpPost = new HttpPost(uri); TestEvent event = new TestEvent(); event.type = "com.example.someevent"; event.source = "/mycontext"; @@ -103,6 +102,7 @@ HttpResponse mockStructuredChatRequest() throws Exception { event.datacontenttype = "text/plain"; event.text = expectedMessage; event.requestType = "CHAT"; + HttpPost httpPost = new HttpPost(uri); httpPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); return httpClient.execute(httpPost); @@ -110,17 +110,16 @@ HttpResponse mockStructuredChatRequest() throws Exception { HttpResponse mockStructuredParseRequest() throws Exception { - HttpPost httpPost = new HttpPost(uri); TestEvent event = new TestEvent(); event.type = "com.example.someevent"; event.source = "/mycontext"; event.subject = "test"; event.datacontenttype = "application/json"; event.text = expectedParseMessage; - event.requestType = "PARSE1"; + event.requestType = "PARSE"; event.fields = "orderNo:this is order number;address:this is a address;phone:this is phone number"; + HttpPost httpPost = new HttpPost(uri); httpPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); - return httpClient.execute(httpPost); } From 126b29d925217fd2d393fb471b64592ab053cf15 Mon Sep 17 00:00:00 2001 From: "shuju.jiang" <1803248734@qq.com> Date: Wed, 10 Apr 2024 18:40:36 +0800 Subject: [PATCH 07/16] [ISSUE #4047] fix dependencies check failed --- .../eventmesh-connector-chatgpt/build.gradle | 2 +- .../connector/ChatGPTSourceConnector.java | 1 - .../connector/ChatGPTSourceConnectorTest.java | 39 ++++++++++++++++++- tools/dependency-check/known-dependencies.txt | 3 ++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle b/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle index d585347a76..b2c45b620f 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/build.gradle @@ -18,9 +18,9 @@ dependencies { api project(":eventmesh-openconnect:eventmesh-openconnect-java") implementation project(":eventmesh-common") + implementation 'com.theokanning.openai-gpt3-java:service:0.18.2' implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0' implementation 'io.vertx:vertx-web:4.4.6' - implementation 'com.theokanning.openai-gpt3-java:service:0.18.2' testImplementation "org.apache.httpcomponents:httpclient" compileOnly 'org.projectlombok:lombok' diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index dc34f855cd..9cbeb2574a 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -35,7 +35,6 @@ import org.apache.commons.lang3.StringUtils; - import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index a9fb17c450..8c20426629 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -20,9 +20,11 @@ import org.apache.eventmesh.common.utils.JsonUtils; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConnectorConfig; +import org.apache.eventmesh.connector.chatgpt.source.config.OpenaiConfig; import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord; import org.apache.eventmesh.openconnect.util.ConfigUtil; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpPost; @@ -37,9 +39,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + class ChatGPTSourceConnectorTest { + private static final Logger LOGGER = LoggerFactory.getLogger("ChatGPTSourceConnectorTest"); + private ChatGPTSourceConnector connector; private ChatGPTSourceConnectorConfig config; private CloseableHttpClient httpClient; @@ -48,9 +55,23 @@ class ChatGPTSourceConnectorTest { private final String expectedParseMessage = "User 13356288979 from Tianjin store placed an order with order number 11221122"; + + public boolean checkOpenAi() throws Exception { + ChatGPTSourceConfig sourceConfig = (ChatGPTSourceConfig) ConfigUtil.parse(connector.configClass()); + OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); + if (StringUtils.isBlank(openaiConfig.getToken())) { + return false; + } + return true; + } + @BeforeEach void setUp() throws Exception { connector = new ChatGPTSourceConnector(); + if (!checkOpenAi()) { + LOGGER.error("please set openai token in the config"); + return; + } ChatGPTSourceConfig sourceConfig = (ChatGPTSourceConfig) ConfigUtil.parse(connector.configClass()); config = sourceConfig.getConnectorConfig(); connector.init(sourceConfig); @@ -63,6 +84,13 @@ void setUp() throws Exception { @Test void testPoll() throws Exception { + ChatGPTSourceConfig sourceConfig = (ChatGPTSourceConfig) ConfigUtil.parse(connector.configClass()); + OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); + if (StringUtils.isBlank(openaiConfig.getToken())) { + LOGGER.error("please set openai token in the config"); + return; + } + final int batchSize = 10; for (int i = 0; i < batchSize; i++) { @@ -125,8 +153,15 @@ HttpResponse mockStructuredParseRequest() throws Exception { @AfterEach void tearDown() throws Exception { - connector.stop(); - httpClient.close(); + if (!checkOpenAi()) { + return; + } + if (connector != null) { + connector.stop(); + } + if (httpClient != null) { + httpClient.close(); + } } class TestEvent { diff --git a/tools/dependency-check/known-dependencies.txt b/tools/dependency-check/known-dependencies.txt index ccf34353e3..81e3f5f6f2 100644 --- a/tools/dependency-check/known-dependencies.txt +++ b/tools/dependency-check/known-dependencies.txt @@ -357,3 +357,6 @@ zookeeper-3.7.1.jar zookeeper-jute-3.7.1.jar zstd-jni-1.5.0-2.jar zstd-jni-1.5.2-2.jar +service-0.18.2.jar +client-0.18.2.jar +api-0.18.2.jar \ No newline at end of file From e79795fbc12732c8f703926613263f8b36d605de Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Wed, 10 Apr 2024 23:22:58 +0800 Subject: [PATCH 08/16] [ISSUE #4047] fix dependencies check --- tools/dependency-check/known-dependencies.txt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tools/dependency-check/known-dependencies.txt b/tools/dependency-check/known-dependencies.txt index 81e3f5f6f2..a5c375b8a2 100644 --- a/tools/dependency-check/known-dependencies.txt +++ b/tools/dependency-check/known-dependencies.txt @@ -1,6 +1,7 @@ FastInfoset-1.2.15.jar ST4-4.3.4.jar accessors-smart-2.4.7.jar +adapter-rxjava2-2.9.0.jar alibabacloud-gateway-spi-0.0.1.jar amqp-client-5.16.0.jar animal-sniffer-annotations-1.19.jar @@ -12,6 +13,7 @@ antlr4-4.13.0.jar antlr4-runtime-4.13.0.jar aopalliance-1.0.jar apache-client-2.20.29.jar +api-0.18.2.jar arns-2.20.29.jar asm-9.1.jar asm-9.2.jar @@ -43,7 +45,9 @@ byte-buddy-1.11.0.jar byte-buddy-1.12.18.jar cache-api-1.1.1.jar checker-qual-3.12.0.jar +classgraph-4.8.21.jar classmate-1.5.1.jar +client-0.18.2.jar cloudevents-api-2.4.2.jar cloudevents-core-2.4.2.jar cloudevents-http-vertx-2.3.0.jar @@ -63,6 +67,7 @@ commons-logging-1.2.jar commons-text-1.9.jar commons-validator-1.7.jar consul-api-1.4.5.jar +converter-jackson-2.9.0.jar credentials-java-0.2.4.jar crt-core-2.20.29.jar curator-client-5.4.0.jar @@ -131,7 +136,6 @@ jaxb-api-2.3.1.jar jaxb-core-2.3.0.jar jaxb-impl-2.3.0.jar jaxb-runtime-2.3.1.jar -jaxen-1.1.6.jar jboss-logging-3.4.1.Final.jar jboss-logging-3.4.3.Final.jar jboss-marshalling-2.0.11.Final.jar @@ -153,6 +157,7 @@ json-path-2.7.0.jar json-smart-2.4.7.jar json-utils-2.20.29.jar jsr305-3.0.2.jar +jtokkit-0.5.1.jar jul-to-slf4j-1.7.33.jar kafka-clients-3.0.0.jar listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar @@ -164,6 +169,7 @@ logback-classic-1.2.10.jar logback-core-1.2.10.jar lz4-java-1.7.1.jar lz4-java-1.8.0.jar +mbknor-jackson-jsonschema_2.12-1.0.34.jar metrics-annotation-4.1.0.jar metrics-core-4.1.0.jar metrics-healthchecks-4.1.0.jar @@ -284,7 +290,6 @@ protobuf-java-util-3.17.2.jar protobuf-java-util-3.21.5.jar protobuf-java-util-3.5.1.jar protocol-core-2.20.29.jar -pull-parser-2.jar pulsar-client-2.10.1.jar pulsar-client-2.11.1.jar pulsar-client-admin-api-2.10.1.jar @@ -295,7 +300,7 @@ reactive-streams-1.0.3.jar reactor-core-3.4.13.jar redisson-3.17.3.jar regions-2.20.29.jar -relaxngDatatype-20020414.jar +retrofit-2.9.0.jar rocketmq-acl-4.9.5.jar rocketmq-broker-4.9.5.jar rocketmq-client-4.9.5.jar @@ -307,9 +312,12 @@ rocketmq-remoting-4.9.5.jar rocketmq-srvutil-4.9.5.jar rocketmq-store-4.9.5.jar rocketmq-tools-4.9.5.jar +rxjava-2.0.0.jar rxjava-3.0.12.jar s3-2.20.29.jar +scala-library-2.12.8.jar sdk-core-2.20.29.jar +service-0.18.2.jar simpleclient-0.8.1.jar simpleclient_common-0.8.1.jar simpleclient_httpserver-0.8.1.jar @@ -331,7 +339,6 @@ spring-core-5.3.20.jar spring-expression-5.3.15.jar spring-jcl-5.3.20.jar spring-messaging-5.3.20.jar -stax-api-1.0-2.jar stax-ex-1.8.jar tea-1.2.7.jar tea-openapi-0.2.8.jar @@ -342,14 +349,13 @@ tomcat-embed-el-9.0.56.jar txw2-2.3.1.jar utils-2.20.29.jar validation-api-1.1.0.Final.jar +validation-api-2.0.1.Final.jar vertx-auth-common-4.4.6.jar vertx-bridge-common-4.4.6.jar vertx-core-4.4.6.jar vertx-web-4.4.6.jar vertx-web-client-4.0.0.jar vertx-web-common-4.4.6.jar -xpp3-1.1.4c.jar -xsdlib-2013.6.1.jar zipkin-2.23.2.jar zipkin-reporter-2.16.3.jar zipkin-sender-okhttp3-2.16.3.jar @@ -357,6 +363,3 @@ zookeeper-3.7.1.jar zookeeper-jute-3.7.1.jar zstd-jni-1.5.0-2.jar zstd-jni-1.5.2-2.jar -service-0.18.2.jar -client-0.18.2.jar -api-0.18.2.jar \ No newline at end of file From 5f600c69782a8e823dc18b01ee07cf54dd204c2b Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Wed, 10 Apr 2024 23:35:50 +0800 Subject: [PATCH 09/16] [ISSUE #4047] fix license check --- tools/dependency-check/known-dependencies.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/dependency-check/known-dependencies.txt b/tools/dependency-check/known-dependencies.txt index a5c375b8a2..be9f03c55e 100644 --- a/tools/dependency-check/known-dependencies.txt +++ b/tools/dependency-check/known-dependencies.txt @@ -136,6 +136,7 @@ jaxb-api-2.3.1.jar jaxb-core-2.3.0.jar jaxb-impl-2.3.0.jar jaxb-runtime-2.3.1.jar +jaxen-1.1.6.jar jboss-logging-3.4.1.Final.jar jboss-logging-3.4.3.Final.jar jboss-marshalling-2.0.11.Final.jar @@ -290,6 +291,7 @@ protobuf-java-util-3.17.2.jar protobuf-java-util-3.21.5.jar protobuf-java-util-3.5.1.jar protocol-core-2.20.29.jar +pull-parser-2.jar pulsar-client-2.10.1.jar pulsar-client-2.11.1.jar pulsar-client-admin-api-2.10.1.jar @@ -300,6 +302,7 @@ reactive-streams-1.0.3.jar reactor-core-3.4.13.jar redisson-3.17.3.jar regions-2.20.29.jar +relaxngDatatype-20020414.jar retrofit-2.9.0.jar rocketmq-acl-4.9.5.jar rocketmq-broker-4.9.5.jar @@ -339,6 +342,7 @@ spring-core-5.3.20.jar spring-expression-5.3.15.jar spring-jcl-5.3.20.jar spring-messaging-5.3.20.jar +stax-api-1.0-2.jar stax-ex-1.8.jar tea-1.2.7.jar tea-openapi-0.2.8.jar @@ -356,6 +360,8 @@ vertx-core-4.4.6.jar vertx-web-4.4.6.jar vertx-web-client-4.0.0.jar vertx-web-common-4.4.6.jar +xpp3-1.1.4c.jar +xsdlib-2013.6.1.jar zipkin-2.23.2.jar zipkin-reporter-2.16.3.jar zipkin-sender-okhttp3-2.16.3.jar From 6ccc5301a5baaef95c5693c84c51b6fb82ec1380 Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Mon, 15 Apr 2024 23:10:44 +0800 Subject: [PATCH 10/16] [ISSUE #4047] fix review question --- .../source/connector/ChatGPTSourceConnector.java | 15 ++++++++++++--- .../chatgpt/source/dto/ChatGPTRequestDTO.java | 8 ++++---- .../chatgpt/source/managers/OpenaiManager.java | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index 9cbeb2574a..b2427f0205 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -96,7 +96,10 @@ public void init(ConnectorContext connectorContext) { public void initParsePrompt() { String parsePromptFileName = sourceConfig.getConnectorConfig().getParsePromptFileName(); URL resource = Thread.currentThread().getContextClassLoader().getResource(parsePromptFileName); - AssertUtils.notNull(resource, String.format("cannot find file %s", parsePromptFileName)); + if (resource == null) { + log.warn("cannot find prompt file {} in resources", parsePromptFileName); + return; + } String filePath = resource.getPath(); try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { StringBuilder builder = new StringBuilder(); @@ -118,7 +121,9 @@ private void doInit() { initParsePrompt(); this.openaiManager = new OpenaiManager(sourceConfig); this.chatHandler = new ChatHandler(this.openaiManager); - this.parseHandler = new ParseHandler(openaiManager, parsePromptTemplateStr); + if (StringUtils.isNotEmpty(parsePromptTemplateStr)) { + this.parseHandler = new ParseHandler(openaiManager, parsePromptTemplateStr); + } this.queue = new LinkedBlockingQueue<>(1024); final Vertx vertx = Vertx.vertx(); final Router router = Router.router(vertx); @@ -139,7 +144,7 @@ private void doInit() { private void validateRequestDTO(ChatGPTRequestDTO bodyObject) { if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getText() == null) { - throw new IllegalArgumentException("Attributes 'subject', 'datacontenttype', and 'prompt' cannot be null"); + throw new IllegalArgumentException("Attributes 'subject', 'datacontenttype', and 'text' cannot be null"); } } @@ -167,6 +172,10 @@ private CloudEvent invokeHandler(ChatGPTRequestType chatgptRequestType, ChatGPTR case CHAT: return chatHandler.invoke(bodyObject); case PARSE: + if (StringUtils.isBlank(parsePromptTemplateStr)) { + throw new IllegalStateException( + "the request type of PARSE must be configured with the correct parsePromptFileName in source-config.yml"); + } return parseHandler.invoke(bodyObject); default: throw new IllegalStateException("the request type is illegal"); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java index 07cac5ca42..dc073c07a9 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -36,14 +36,14 @@ public class ChatGPTRequestDTO { private String requestType = ChatGPTRequestType.CHAT.name(); - private String source; + private String source = "/"; - private String subject; + private String subject = "chatGPT"; @JsonProperty("datacontenttype") - private String dataContentType; + private String dataContentType = "application/json"; - private String type; + private String type = "cloudevents"; private String text; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java index 9e97e6cdbc..246e0b890b 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java @@ -17,6 +17,7 @@ package org.apache.eventmesh.connector.chatgpt.source.managers; +import static com.theokanning.openai.service.OpenAiService.defaultClient; import static com.theokanning.openai.service.OpenAiService.defaultObjectMapper; import static com.theokanning.openai.service.OpenAiService.defaultRetrofit; @@ -85,8 +86,7 @@ private void initOpenAi(ChatGPTSourceConfig sourceConfig) { } ObjectMapper mapper = defaultObjectMapper(); Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(chatgptProxyConfig.getHost(), chatgptProxyConfig.getPort())); - OkHttpClient client = - OpenAiService.defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())).newBuilder().proxy(proxy).build(); + OkHttpClient client = defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())).newBuilder().proxy(proxy).build(); Retrofit retrofit = defaultRetrofit(client, mapper); OpenAiApi api = retrofit.create(OpenAiApi.class); this.openAiService = new OpenAiService(api); From 010e2a98e39f97471b8d4036022618a3f0a4aca0 Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Tue, 16 Apr 2024 22:27:27 +0800 Subject: [PATCH 11/16] [ISSUE #4047] fix review question --- .../connector/chatgpt/source/config/OpenaiConfig.java | 4 ++-- .../source/connector/ChatGPTSourceConnector.java | 1 - .../chatgpt/source/managers/OpenaiManager.java | 11 +++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java index 1954c74ddd..58ac1cea39 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -28,7 +28,7 @@ public class OpenaiConfig { private String token; private String model; - private long timeout; + private long timeout = 30; private Double temperature; private Integer maxTokens; private Boolean logprob; @@ -36,7 +36,7 @@ public class OpenaiConfig { private Map logitBias; private Double frequencyPenalty; private Double presencePenalty; - private String user; + private String user = "eventMesh"; private List stop; } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index b2427f0205..3d1465a85f 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -19,7 +19,6 @@ import org.apache.eventmesh.common.ThreadPoolFactory; import org.apache.eventmesh.common.exception.EventMeshException; -import org.apache.eventmesh.common.utils.AssertUtils; import org.apache.eventmesh.connector.chatgpt.source.config.ChatGPTSourceConfig; import org.apache.eventmesh.connector.chatgpt.source.dto.ChatGPTRequestDTO; import org.apache.eventmesh.connector.chatgpt.source.enums.ChatGPTRequestType; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java index 246e0b890b..570fb39728 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java @@ -54,6 +54,8 @@ public class OpenaiManager { private String chatCompletionRequestTemplateStr; + private static final int DEFAULT_TIMEOUT = 30; + public OpenaiManager(ChatGPTSourceConfig sourceConfig) { initOpenAi(sourceConfig); } @@ -77,7 +79,11 @@ public ChatCompletionRequest newChatCompletionRequest(List chatMess private void initOpenAi(ChatGPTSourceConfig sourceConfig) { OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); - AssertUtils.isTrue(openaiConfig.getTimeout() > 0, "openaiTimeout must be >= 0"); + if (openaiConfig.getTimeout() <= 0) { + log.warn("openaiTimeout must be > 0, your config value is {}, openaiTimeout will be reset {}", openaiConfig.getTimeout(), + DEFAULT_TIMEOUT); + openaiConfig.setTimeout(DEFAULT_TIMEOUT); + } boolean proxyEnable = sourceConfig.connectorConfig.isProxyEnable(); if (proxyEnable) { OpenaiProxyConfig chatgptProxyConfig = sourceConfig.openaiProxyConfig; @@ -86,7 +92,8 @@ private void initOpenAi(ChatGPTSourceConfig sourceConfig) { } ObjectMapper mapper = defaultObjectMapper(); Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(chatgptProxyConfig.getHost(), chatgptProxyConfig.getPort())); - OkHttpClient client = defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())).newBuilder().proxy(proxy).build(); + OkHttpClient client = + defaultClient(openaiConfig.getToken(), Duration.ofSeconds(openaiConfig.getTimeout())).newBuilder().proxy(proxy).build(); Retrofit retrofit = defaultRetrofit(client, mapper); OpenAiApi api = retrofit.create(OpenAiApi.class); this.openAiService = new OpenAiService(api); From 7bfadbb73312a429b49319050a94e91730730a8c Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Wed, 17 Apr 2024 21:29:24 +0800 Subject: [PATCH 12/16] [ISSUE #4047] add default value --- .../source/config/ChatGPTSourceConnectorConfig.java | 12 ++++++------ .../chatgpt/source/config/OpenaiConfig.java | 2 +- .../source/connector/ChatGPTSourceConnector.java | 12 ++++++++++-- .../src/main/resources/source-config.yml | 10 ++++------ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java index 8e1eba1410..3a129a9f32 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -22,16 +22,16 @@ @Data public class ChatGPTSourceConnectorConfig { - private String connectorName; + private String connectorName = "chatgptSource"; - private String path; + private String path = "/chatgpt"; - private int port; + private int port = 3756; - private int idleTimeout; + private int idleTimeout = 30; - private boolean proxyEnable; + private boolean proxyEnable = false; - private String parsePromptFileName; + private String parsePromptFileName = "prompt"; } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java index 58ac1cea39..71c50e84c4 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -27,7 +27,7 @@ public class OpenaiConfig { private String token; - private String model; + private String model = "gpt-3.5-turbo"; private long timeout = 30; private Double temperature; private Integer maxTokens; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index 3d1465a85f..8f036a604d 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -66,13 +66,16 @@ public class ChatGPTSourceConnector implements Source { private ChatGPTSourceConfig sourceConfig; private BlockingQueue queue; private HttpServer server; - private final ExecutorService chatgptSourceExecutorService = ThreadPoolFactory.createThreadPoolExecutor( - Runtime.getRuntime().availableProcessors() * 2, Runtime.getRuntime().availableProcessors() * 2, "ChatGPTSourceThread"); + private final ExecutorService chatgptSourceExecutorService = + ThreadPoolFactory.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2, Runtime.getRuntime().availableProcessors() * 2, + "ChatGPTSourceThread"); private OpenaiManager openaiManager; private String parsePromptTemplateStr; private ChatHandler chatHandler; private ParseHandler parseHandler; + private static final int DEFAULT_TIMEOUT = 30; + @Override public Class configClass() { @@ -136,6 +139,11 @@ private void doInit() { handleError(e, ctx); } }); + if (sourceConfig.connectorConfig.getIdleTimeout() <= 0) { + log.warn("idleTimeout must be > 0, your config value is {}, idleTimeout will be reset {}", sourceConfig.connectorConfig.getIdleTimeout(), + DEFAULT_TIMEOUT); + sourceConfig.connectorConfig.setIdleTimeout(DEFAULT_TIMEOUT); + } this.server = vertx.createHttpServer(new HttpServerOptions().setPort(this.sourceConfig.connectorConfig.getPort()) .setIdleTimeout(this.sourceConfig.connectorConfig.getIdleTimeout())).requestHandler(router); } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml index 28476ee85a..b68eb8dfc8 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml @@ -28,24 +28,22 @@ connectorConfig: connectorName: chatgptSource path: /chatgpt port: 3756 - idleTimeout: 999 - proxyEnable: true + idleTimeout: 30 + proxyEnable: false parsePromptFileName: prompt # https://platform.openai.com/docs/api-reference/chat/create openaiConfig: token: model: gpt-3.5-turbo - timeout: 60 + timeout: 30 temperature: 1 maxTokens: frequencyPenalty: 0 presencePenalty: 0 user: eventMesh stop: [] - logitBias: { - - } + logitBias: {} openaiProxyConfig: host: 127.0.0.1 From c84991274d447ed5beacfec870b4d0b173395f11 Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Wed, 17 Apr 2024 21:48:46 +0800 Subject: [PATCH 13/16] [ISSUE #4047] fix test --- .../source/connector/ChatGPTSourceConnectorTest.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java index 8c20426629..8347fdcbb1 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnectorTest.java @@ -108,17 +108,6 @@ void testPoll() throws Exception { List res1 = connector.poll(); Assertions.assertEquals(batchSize, res1.size()); - - // test invalid requests - TestEvent event = new TestEvent(); - event.type = "com.example.someevent"; - event.source = "/mycontext"; - event.datacontenttype = "text/plain"; - event.text = expectedMessage; - HttpPost invalidPost = new HttpPost(uri); - invalidPost.setEntity(new StringEntity(JsonUtils.toJSONString(event))); - HttpResponse resp = httpClient.execute(invalidPost); - Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, resp.getStatusLine().getStatusCode()); } From 0bffda88d83087b460eb4e88834a38151365fc2e Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Sat, 20 Apr 2024 01:42:57 +0800 Subject: [PATCH 14/16] [ISSUE #4047] default timeout value is zero , not timeout . --- .../config/ChatGPTSourceConnectorConfig.java | 2 +- .../chatgpt/source/config/OpenaiConfig.java | 2 +- .../connector/ChatGPTSourceConnector.java | 22 ++++++++++++++----- .../chatgpt/source/dto/ChatGPTRequestDTO.java | 2 +- .../source/managers/OpenaiManager.java | 6 ++--- .../src/main/resources/source-config.yml | 4 ++-- .../src/test/resources/source-config.yml | 4 ++-- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java index 3a129a9f32..5e14e2439f 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -28,7 +28,7 @@ public class ChatGPTSourceConnectorConfig { private int port = 3756; - private int idleTimeout = 30; + private int idleTimeout = 0; private boolean proxyEnable = false; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java index 71c50e84c4..00b08cb111 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -28,7 +28,7 @@ public class OpenaiConfig { private String token; private String model = "gpt-3.5-turbo"; - private long timeout = 30; + private long timeout = 0; private Double temperature; private Integer maxTokens; private Boolean logprob; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index 8f036a604d..a5d79101e2 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -74,7 +74,10 @@ public class ChatGPTSourceConnector implements Source { private String parsePromptTemplateStr; private ChatHandler chatHandler; private ParseHandler parseHandler; - private static final int DEFAULT_TIMEOUT = 30; + private static final int DEFAULT_TIMEOUT = 0; + + private static final String APPLICATION_JSON = "application/json"; + private static final String TEXT_PLAIN = "text/plain"; @Override @@ -139,8 +142,8 @@ private void doInit() { handleError(e, ctx); } }); - if (sourceConfig.connectorConfig.getIdleTimeout() <= 0) { - log.warn("idleTimeout must be > 0, your config value is {}, idleTimeout will be reset {}", sourceConfig.connectorConfig.getIdleTimeout(), + if (sourceConfig.connectorConfig.getIdleTimeout() < 0) { + log.warn("idleTimeout must be >= 0, your config value is {}, idleTimeout will be reset {}", sourceConfig.connectorConfig.getIdleTimeout(), DEFAULT_TIMEOUT); sourceConfig.connectorConfig.setIdleTimeout(DEFAULT_TIMEOUT); } @@ -150,8 +153,8 @@ private void doInit() { private void validateRequestDTO(ChatGPTRequestDTO bodyObject) { - if (bodyObject.getSubject() == null || bodyObject.getDataContentType() == null || bodyObject.getText() == null) { - throw new IllegalArgumentException("Attributes 'subject', 'datacontenttype', and 'text' cannot be null"); + if (bodyObject.getSubject() == null || bodyObject.getText() == null) { + throw new IllegalArgumentException("Attributes 'subject', and 'text' cannot be null"); } } @@ -177,12 +180,21 @@ private void handleRequest(ChatGPTRequestDTO bodyObject, RoutingContext ctx) { private CloudEvent invokeHandler(ChatGPTRequestType chatgptRequestType, ChatGPTRequestDTO bodyObject) { switch (chatgptRequestType) { case CHAT: + if (StringUtils.isBlank(bodyObject.getDataContentType())) { + bodyObject.setDataContentType(TEXT_PLAIN); + } return chatHandler.invoke(bodyObject); case PARSE: if (StringUtils.isBlank(parsePromptTemplateStr)) { throw new IllegalStateException( "the request type of PARSE must be configured with the correct parsePromptFileName in source-config.yml"); } + if (StringUtils.isBlank(bodyObject.getFields())) { + throw new IllegalStateException("Attributes 'fields' cannot be null in PARSE"); + } + if (StringUtils.isBlank(bodyObject.getDataContentType())) { + bodyObject.setDataContentType(APPLICATION_JSON); + } return parseHandler.invoke(bodyObject); default: throw new IllegalStateException("the request type is illegal"); diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java index dc073c07a9..a203a24e5a 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/dto/ChatGPTRequestDTO.java @@ -41,7 +41,7 @@ public class ChatGPTRequestDTO { private String subject = "chatGPT"; @JsonProperty("datacontenttype") - private String dataContentType = "application/json"; + private String dataContentType; private String type = "cloudevents"; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java index 570fb39728..fda5216bbf 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/managers/OpenaiManager.java @@ -54,7 +54,7 @@ public class OpenaiManager { private String chatCompletionRequestTemplateStr; - private static final int DEFAULT_TIMEOUT = 30; + private static final int DEFAULT_TIMEOUT = 0; public OpenaiManager(ChatGPTSourceConfig sourceConfig) { initOpenAi(sourceConfig); @@ -79,8 +79,8 @@ public ChatCompletionRequest newChatCompletionRequest(List chatMess private void initOpenAi(ChatGPTSourceConfig sourceConfig) { OpenaiConfig openaiConfig = sourceConfig.getOpenaiConfig(); - if (openaiConfig.getTimeout() <= 0) { - log.warn("openaiTimeout must be > 0, your config value is {}, openaiTimeout will be reset {}", openaiConfig.getTimeout(), + if (openaiConfig.getTimeout() < 0) { + log.warn("openaiTimeout must be >= 0, your config value is {}, openaiTimeout will be reset {}", openaiConfig.getTimeout(), DEFAULT_TIMEOUT); openaiConfig.setTimeout(DEFAULT_TIMEOUT); } diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml index b68eb8dfc8..b194e99ecb 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/resources/source-config.yml @@ -28,7 +28,7 @@ connectorConfig: connectorName: chatgptSource path: /chatgpt port: 3756 - idleTimeout: 30 + idleTimeout: 0 proxyEnable: false parsePromptFileName: prompt @@ -36,7 +36,7 @@ connectorConfig: openaiConfig: token: model: gpt-3.5-turbo - timeout: 30 + timeout: 0 temperature: 1 maxTokens: frequencyPenalty: 0 diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml index ee4fd9dee8..47f25edbb2 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/test/resources/source-config.yml @@ -28,7 +28,7 @@ connectorConfig: connectorName: chatgptSource path: /chatgpt port: 3756 - idleTimeout: 999 + idleTimeout: 0 proxyEnable: true parsePromptFileName: prompt @@ -36,7 +36,7 @@ connectorConfig: openaiConfig: token: model: gpt-3.5-turbo - timeout: 60 + timeout: 0 temperature: 1 maxTokens: frequencyPenalty: 0 From 7343c76274dffb9ca72e2e15c6f0eb6987a5457a Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Sat, 20 Apr 2024 14:59:44 +0800 Subject: [PATCH 15/16] [ISSUE #4047] fix review --- .../chatgpt/source/config/ChatGPTSourceConnectorConfig.java | 2 +- .../connector/chatgpt/source/config/OpenaiConfig.java | 2 +- .../chatgpt/source/connector/ChatGPTSourceConnector.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java index 5e14e2439f..316fb5f241 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/ChatGPTSourceConnectorConfig.java @@ -28,7 +28,7 @@ public class ChatGPTSourceConnectorConfig { private int port = 3756; - private int idleTimeout = 0; + private int idleTimeout; private boolean proxyEnable = false; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java index 00b08cb111..51858a709a 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/config/OpenaiConfig.java @@ -28,7 +28,7 @@ public class OpenaiConfig { private String token; private String model = "gpt-3.5-turbo"; - private long timeout = 0; + private long timeout; private Double temperature; private Integer maxTokens; private Boolean logprob; diff --git a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java index a5d79101e2..a947bc135d 100644 --- a/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java +++ b/eventmesh-connectors/eventmesh-connector-chatgpt/src/main/java/org/apache/eventmesh/connector/chatgpt/source/connector/ChatGPTSourceConnector.java @@ -153,8 +153,8 @@ private void doInit() { private void validateRequestDTO(ChatGPTRequestDTO bodyObject) { - if (bodyObject.getSubject() == null || bodyObject.getText() == null) { - throw new IllegalArgumentException("Attributes 'subject', and 'text' cannot be null"); + if (StringUtils.isBlank(bodyObject.getText())) { + throw new IllegalArgumentException("Attributes 'text' cannot be null"); } } From 86bf1397233a3a0c4ec8d79b1170d28dd9552951 Mon Sep 17 00:00:00 2001 From: JiangShuJu Date: Sat, 20 Apr 2024 16:27:56 +0800 Subject: [PATCH 16/16] [ISSUE #4047] fix license check --- tools/dependency-check/known-dependencies.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/dependency-check/known-dependencies.txt b/tools/dependency-check/known-dependencies.txt index d830d3f286..73feb5fd3b 100644 --- a/tools/dependency-check/known-dependencies.txt +++ b/tools/dependency-check/known-dependencies.txt @@ -161,7 +161,6 @@ json-smart-2.4.7.jar json-utils-2.20.29.jar jsr305-3.0.2.jar jtokkit-0.5.1.jar -jul-to-slf4j-1.7.33.jar kafka-clients-3.0.0.jar listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar log4j-api-2.22.1.jar