diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index 19727fdec30..6cf0e071d6b 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -846,4 +846,30 @@
fields to be excluded from being saved in note files, with Paragraph prefix mean the fields in Paragraph, e.g. Paragraph.results
+
+ zeppelin.assistant.enable
+ false
+
+
+
+ zeppelin.assistant.openai.api.key
+
+
+
+
+ zeppelin.assistant.openai.endpoint
+ https://api.openai.com/v1
+
+
+
+ zeppelin.assistant.openai.model
+ gpt-6-luna
+
+
+
+ zeppelin.assistant.dir
+ assistant
+ Directory for assistant conversations.
+
+
diff --git a/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java b/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
index d1e1606007b..04a3f79cc2b 100644
--- a/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
+++ b/zeppelin-common/src/main/java/org/apache/zeppelin/common/Message.java
@@ -203,7 +203,9 @@ public enum OP {
COLLABORATIVE_MODE_STATUS, // [s-c] collaborative mode status
PATCH_PARAGRAPH, // [c-s][s-c] patch editor text
NOTE_RUNNING_STATUS, // [s-c] sequential run status will be change
- NOTICE // [s-c] Notice
+ NOTICE, // [s-c] Notice
+ ASSISTANT_SEND_MESSAGE, // [c-s] send a message to the notebook assistant
+ ASSISTANT_EVENT // [s-c] assistant run stream event (see "type" field)
}
// these messages will be ignored during the sequential run of the note
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 3c6939d4030..09594969c90 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -1,5 +1,25 @@
The following components are provided under Apache License.
+ (Apache 2.0) ClassMate (com.fasterxml:classmate:1.7.0 - https://github.com/FasterXML/java-classmate)
+ (Apache 2.0) Jackson JDK8 Datatypes (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.3 - https://github.com/FasterXML/jackson-modules-java8)
+ (Apache 2.0) Jackson Java Time Datatypes (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.3 - https://github.com/FasterXML/jackson-modules-java8)
+ (Apache 2.0) Jackson Kotlin Module (com.fasterxml.jackson.module:jackson-module-kotlin:2.18.3 - https://github.com/FasterXML/jackson-module-kotlin)
+ (Apache 2.0) JSON Schema Generator (com.github.victools:jsonschema-generator:4.38.0 - https://github.com/victools/jsonschema-generator)
+ (Apache 2.0) JSON Schema Generator (com.github.victools:jsonschema-module-jackson:4.38.0 - https://github.com/victools/jsonschema-generator)
+ (Apache 2.0) JSON Schema Generator (com.github.victools:jsonschema-module-swagger-2:4.38.0 - https://github.com/victools/jsonschema-generator)
+ (Apache 2.0) OpenAI Java SDK (com.openai:openai-java:4.69.3 - https://github.com/openai/openai-java)
+ (Apache 2.0) OpenAI Java SDK (com.openai:openai-java-client-okhttp:4.69.3 - https://github.com/openai/openai-java)
+ (Apache 2.0) OpenAI Java SDK (com.openai:openai-java-core:4.69.3 - https://github.com/openai/openai-java)
+ (Apache 2.0) OkHttp (com.squareup.okhttp3:okhttp:4.12.0 - https://github.com/square/okhttp)
+ (Apache 2.0) Okio (com.squareup.okio:okio:3.6.0 - https://github.com/square/okio)
+ (Apache 2.0) Okio (com.squareup.okio:okio-jvm:3.6.0 - https://github.com/square/okio)
+ (Apache 2.0) Swagger Annotations (io.swagger.core.v3:swagger-annotations:2.2.31 - https://github.com/swagger-api/swagger-core)
+ (Apache 2.0) Kotlin (org.jetbrains.kotlin:kotlin-reflect:1.9.10 - https://github.com/JetBrains/kotlin)
+ (Apache 2.0) Kotlin (org.jetbrains.kotlin:kotlin-stdlib:1.9.10 - https://github.com/JetBrains/kotlin)
+ (Apache 2.0) Kotlin (org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10 - https://github.com/JetBrains/kotlin)
+ (Apache 2.0) Kotlin (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 - https://github.com/JetBrains/kotlin)
+ (Apache 2.0) Kotlin (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 - https://github.com/JetBrains/kotlin)
+
(Apache 2.0) nvd3.js v1.7.1 (http://nvd3.org/) - https://github.com/novus/nvd3/blob/v1.7.1/LICENSE.md
(Apache 2.0) gson v2.8.9 (com.google.code.gson:gson:jar:2.8.9 - https://github.com/google/gson) - https://github.com/google/gson/blob/gson-parent-2.8.9/LICENSE
(Apache 2.0) Amazon Web Services SDK for Java v1.11.736 (https://aws.amazon.com/sdk-for-java/) - https://raw.githubusercontent.com/aws/aws-sdk-java/1.11.736/LICENSE.txt
diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml
index 505c5ed75bb..7191c0bb017 100644
--- a/zeppelin-server/pom.xml
+++ b/zeppelin-server/pom.xml
@@ -432,6 +432,71 @@
gson
+
+ com.openai
+ openai-java
+ 4.69.3
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+ org.jetbrains
+ annotations
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ 1.9.10
+
+
+
+ org.jetbrains
+ annotations
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ 1.9.10
+ runtime
+
+
+
+ org.jetbrains
+ annotations
+
+
+
+
+
+ org.jetbrains
+ annotations
+ 15.0
+
+
+
+ com.google.errorprone
+ error_prone_annotations
+ 2.18.0
+
+
com.nimbusds
nimbus-jose-jwt
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 6a7e1b7909f..8b161d174f0 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -888,6 +888,26 @@ public boolean isZeppelinSearchSemanticEnable() {
return getBoolean(ConfVars.ZEPPELIN_SEARCH_SEMANTIC_ENABLE);
}
+ public boolean isAssistantEnabled() {
+ return getBoolean(ConfVars.ZEPPELIN_ASSISTANT_ENABLE);
+ }
+
+ public String getAssistantApiKey() {
+ return getString(ConfVars.ZEPPELIN_ASSISTANT_OPENAI_API_KEY);
+ }
+
+ public String getAssistantEndpoint() {
+ return getString(ConfVars.ZEPPELIN_ASSISTANT_OPENAI_ENDPOINT);
+ }
+
+ public String getAssistantModel() {
+ return getString(ConfVars.ZEPPELIN_ASSISTANT_OPENAI_MODEL);
+ }
+
+ public String getAssistantDir() {
+ return getAbsoluteDir(ConfVars.ZEPPELIN_ASSISTANT_DIR);
+ }
+
public boolean isOnlyYarnCluster() {
return getBoolean(ConfVars.ZEPPELIN_SPARK_ONLY_YARN_CLUSTER);
}
@@ -1193,7 +1213,16 @@ public enum ConfVars {
ZEPPELIN_SPARK_ONLY_YARN_CLUSTER("zeppelin.spark.only_yarn_cluster", false),
ZEPPELIN_SESSION_CHECK_INTERVAL("zeppelin.session.check_interval", 60 * 10 * 1000),
ZEPPELIN_NOTE_CACHE_THRESHOLD("zeppelin.note.cache.threshold", 50),
- ZEPPELIN_NOTE_FILE_EXCLUDE_FIELDS("zeppelin.note.file.exclude.fields", "");
+ ZEPPELIN_NOTE_FILE_EXCLUDE_FIELDS("zeppelin.note.file.exclude.fields", ""),
+ ZEPPELIN_ASSISTANT_ENABLE("zeppelin.assistant.enable", false),
+ ZEPPELIN_ASSISTANT_DIR("zeppelin.assistant.dir", "assistant"),
+ ZEPPELIN_ASSISTANT_OPENAI_API_KEY("zeppelin.assistant.openai.api.key", ""),
+ ZEPPELIN_ASSISTANT_OPENAI_ENDPOINT(
+ "zeppelin.assistant.openai.endpoint", "https://api.openai.com/v1"
+ ),
+ ZEPPELIN_ASSISTANT_OPENAI_MODEL(
+ "zeppelin.assistant.openai.model", "gpt-6-luna"
+ );
private String varName;
private Class> varClass;
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantConversationRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantConversationRestApi.java
new file mode 100644
index 00000000000..4d3b8f52012
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantConversationRestApi.java
@@ -0,0 +1,157 @@
+/*
+ * 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.zeppelin.rest;
+
+import com.google.gson.JsonSyntaxException;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PATCH;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.zeppelin.annotation.ZeppelinApi;
+import org.apache.zeppelin.rest.message.ConversationRequest;
+import org.apache.zeppelin.rest.message.ConversationMetadata;
+import org.apache.zeppelin.rest.message.ConversationResponse;
+import org.apache.zeppelin.server.JsonResponse;
+import org.apache.zeppelin.service.AuthenticationService;
+import org.apache.zeppelin.service.assistant.Assistant;
+
+@Path("/notes/{noteId}/conversations")
+@Produces("application/json")
+@Singleton
+public class AssistantConversationRestApi extends AbstractRestApi {
+
+ private final Assistant assistant;
+
+ @Inject
+ public AssistantConversationRestApi(
+ AuthenticationService authenticationService,
+ Assistant assistant
+ ) {
+ super(authenticationService);
+ this.assistant = assistant;
+ }
+
+ private static ConversationRequest parseBody(String body) {
+ try {
+ return GSON.fromJson(body, ConversationRequest.class);
+ } catch (JsonSyntaxException e) {
+ throw new BadRequestException();
+ }
+ }
+
+ @GET
+ @ZeppelinApi
+ public Response list(@PathParam("noteId") String noteId) throws IOException {
+ var context = getServiceContext();
+ return new JsonResponse<>(
+ Response.Status.OK,
+ "",
+ assistant.listConversations(noteId, context.getUserAndRoles()).stream()
+ .map(c -> ConversationMetadata.of(c, context.getAutheInfo().getUser()))
+ .collect(Collectors.toList())
+ ).build();
+ }
+
+ @GET
+ @Path("/{conversationId}")
+ @ZeppelinApi
+ public Response get(
+ @PathParam("noteId") String noteId,
+ @PathParam("conversationId") String conversationId
+ ) throws IOException {
+ var context = getServiceContext();
+ var conversation = assistant.getConversation(
+ noteId,
+ conversationId,
+ context.getUserAndRoles()
+ );
+ return new JsonResponse<>(
+ Response.Status.OK,
+ "",
+ ConversationResponse.of(conversation, context.getAutheInfo().getUser())
+ ).build();
+ }
+
+ @POST
+ @ZeppelinApi
+ public Response create(
+ @PathParam("noteId") String noteId,
+ String body
+ ) throws IOException {
+ String title = Optional.ofNullable(parseBody(body))
+ .map(ConversationRequest::getTitle)
+ .orElse(null);
+ var context = getServiceContext();
+ var conversation = assistant.createConversation(
+ noteId, title, context.getAutheInfo(), context.getUserAndRoles()
+ );
+ return new JsonResponse<>(
+ Response.Status.CREATED,
+ "",
+ ConversationMetadata.of(conversation, context.getAutheInfo().getUser())
+ ).build();
+ }
+
+ @PATCH
+ @Path("/{conversationId}")
+ @ZeppelinApi
+ public Response updateTitle(
+ @PathParam("noteId") String noteId,
+ @PathParam("conversationId") String conversationId,
+ String body
+ ) throws IOException {
+ String title = Optional.ofNullable(parseBody(body))
+ .map(ConversationRequest::getTitle)
+ .filter(t -> !t.isBlank())
+ .orElseThrow(() -> new BadRequestException("title is required"));
+ var context = getServiceContext();
+ var conversation = assistant.updateTitle(
+ noteId, conversationId, title, context.getAutheInfo().getUser(), context.getUserAndRoles());
+ return new JsonResponse<>(
+ Response.Status.OK,
+ "",
+ ConversationMetadata.of(conversation, context.getAutheInfo().getUser())
+ ).build();
+ }
+
+ @DELETE
+ @Path("/{conversationId}")
+ @ZeppelinApi
+ public Response delete(
+ @PathParam("noteId") String noteId,
+ @PathParam("conversationId") String conversationId
+ ) throws IOException {
+ var context = getServiceContext();
+ assistant.deleteConversation(
+ noteId, conversationId, context.getAutheInfo().getUser(), context.getUserAndRoles());
+ return Response.noContent().build();
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantMessageRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantMessageRestApi.java
new file mode 100644
index 00000000000..9d46f6ab028
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/AssistantMessageRestApi.java
@@ -0,0 +1,71 @@
+/*
+ * 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.zeppelin.rest;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Response;
+
+import java.io.IOException;
+
+import org.apache.zeppelin.annotation.ZeppelinApi;
+import org.apache.zeppelin.rest.message.MessagesResponse;
+import org.apache.zeppelin.server.JsonResponse;
+import org.apache.zeppelin.service.AuthenticationService;
+import org.apache.zeppelin.service.assistant.Assistant;
+
+@Path("/notes/{noteId}/conversations/{conversationId}/messages")
+@Produces("application/json")
+@Singleton
+public class AssistantMessageRestApi extends AbstractRestApi {
+
+ private final Assistant assistant;
+
+ @Inject
+ public AssistantMessageRestApi(
+ AuthenticationService authenticationService,
+ Assistant assistant
+ ) {
+ super(authenticationService);
+ this.assistant = assistant;
+ }
+
+ @GET
+ @ZeppelinApi
+ public Response list(
+ @PathParam("noteId") String noteId,
+ @PathParam("conversationId") String conversationId,
+ @QueryParam("cursor") String cursor,
+ @QueryParam("limit") @DefaultValue("10") int limit
+ ) throws IOException {
+ return new JsonResponse<>(
+ Response.Status.OK,
+ "",
+ MessagesResponse.of(
+ assistant.listMessages(
+ noteId, conversationId, cursor, limit, getServiceContext().getUserAndRoles()
+ ))
+ ).build();
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationMetadata.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationMetadata.java
new file mode 100644
index 00000000000..408428c7d42
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationMetadata.java
@@ -0,0 +1,55 @@
+/*
+ * 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.zeppelin.rest.message;
+
+import org.apache.zeppelin.service.assistant.Conversation;
+
+public final class ConversationMetadata {
+ private final String id;
+ private final String noteId;
+ private final String ownerId;
+ private final String title;
+ private final String createdAt;
+ private final String updatedAt;
+ private final boolean canSendMessage;
+
+ private ConversationMetadata(
+ String id,
+ String noteId,
+ String ownerId,
+ String title,
+ String createdAt,
+ String updatedAt,
+ boolean canSendMessage
+ ) {
+ this.id = id;
+ this.noteId = noteId;
+ this.ownerId = ownerId;
+ this.title = title;
+ this.createdAt = createdAt;
+ this.updatedAt = updatedAt;
+ this.canSendMessage = canSendMessage;
+ }
+
+ public static ConversationMetadata of(Conversation c, String userId) {
+ return new ConversationMetadata(
+ c.getId(), c.getNoteId(), c.getOwnerId(), c.getTitle(), c.getCreatedAt(), c.getUpdatedAt(),
+ c.isOwner(userId)
+ );
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationRequest.java
new file mode 100644
index 00000000000..9ecbf0dbf75
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationRequest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.zeppelin.rest.message;
+
+public class ConversationRequest {
+ private final String title;
+
+ public ConversationRequest(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationResponse.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationResponse.java
new file mode 100644
index 00000000000..e747be73b7f
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/ConversationResponse.java
@@ -0,0 +1,48 @@
+/*
+ * 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.zeppelin.rest.message;
+
+import java.util.List;
+import org.apache.zeppelin.service.assistant.Conversation;
+import org.apache.zeppelin.service.assistant.Message;
+
+public final class ConversationResponse {
+ private final String id;
+ private final String noteId;
+ private final String ownerId;
+ private final String title;
+ private final String createdAt;
+ private final String updatedAt;
+ private final boolean canSendMessage;
+ private final List messages;
+
+ private ConversationResponse(Conversation conversation, String userId) {
+ this.id = conversation.getId();
+ this.noteId = conversation.getNoteId();
+ this.ownerId = conversation.getOwnerId();
+ this.title = conversation.getTitle();
+ this.createdAt = conversation.getCreatedAt();
+ this.updatedAt = conversation.getUpdatedAt();
+ this.canSendMessage = conversation.isOwner(userId);
+ this.messages = List.copyOf(conversation.getMessages());
+ }
+
+ public static ConversationResponse of(Conversation conversation, String userId) {
+ return new ConversationResponse(conversation, userId);
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/MessagesResponse.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/MessagesResponse.java
new file mode 100644
index 00000000000..e026886cba0
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/MessagesResponse.java
@@ -0,0 +1,36 @@
+/*
+ * 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.zeppelin.rest.message;
+
+import java.util.List;
+import org.apache.zeppelin.service.assistant.Message;
+import org.apache.zeppelin.service.assistant.MessagePage;
+
+public final class MessagesResponse {
+ private final List messages;
+ private final String cursor;
+
+ private MessagesResponse(MessagePage page) {
+ this.messages = List.copyOf(page.messages);
+ this.cursor = page.cursor;
+ }
+
+ public static MessagesResponse of(MessagePage page) {
+ return new MessagesResponse(page);
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/RestApiApplication.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/RestApiApplication.java
index b43fe0111af..79ecf80f84d 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/RestApiApplication.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/RestApiApplication.java
@@ -23,6 +23,8 @@
import jakarta.ws.rs.core.Application;
+import org.apache.zeppelin.rest.AssistantConversationRestApi;
+import org.apache.zeppelin.rest.AssistantMessageRestApi;
import org.apache.zeppelin.rest.AdminRestApi;
import org.apache.zeppelin.rest.ConfigurationsRestApi;
import org.apache.zeppelin.rest.CredentialRestApi;
@@ -43,6 +45,8 @@ public class RestApiApplication extends Application {
@Override
public Set> getClasses() {
Set> s = new HashSet<>();
+ s.add(AssistantConversationRestApi.class);
+ s.add(AssistantMessageRestApi.class);
s.add(AdminRestApi.class);
s.add(ConfigurationsRestApi.class);
s.add(CredentialRestApi.class);
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index 480422b4a5b..af255aa2c6c 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -92,6 +92,8 @@
import org.apache.zeppelin.search.NoSearchService;
import org.apache.zeppelin.search.SearchService;
import org.apache.zeppelin.service.*;
+import org.apache.zeppelin.service.assistant.Assistant;
+import org.apache.zeppelin.service.assistant.AssistantModule;
import org.apache.zeppelin.service.AuthenticationService;
import org.apache.zeppelin.service.auth.AuthenticationServiceFactory;
import org.apache.zeppelin.socket.ConnectionManager;
@@ -139,6 +141,7 @@ public class ZeppelinServer implements AutoCloseable {
private final Server jettyWebServer;
private final ServiceLocator sharedServiceLocator;
private final ConfigStorage storage;
+ private AssistantModule assistantModule;
public ZeppelinServer(ZeppelinConfiguration zConf) throws IOException {
this(zConf, DEFAULT_SERVICE_LOCATOR_NAME);
@@ -255,6 +258,24 @@ protected void configure() {
// Lazy loading will cause paragraph recovery and cron job initialization is delayed.
Notebook notebook = ServiceLocatorUtilities.getService(
sharedServiceLocator, Notebook.class.getName());
+
+ assistantModule = new AssistantModule(
+ zConf.isAssistantEnabled(),
+ zConf.getAssistantEndpoint(),
+ zConf.getAssistantApiKey(),
+ zConf.getAssistantModel(),
+ zConf.getAssistantDir(),
+ notebook,
+ sharedServiceLocator.getService(NotebookService.class),
+ sharedServiceLocator.getService(AuthorizationService.class)
+ );
+ ServiceLocatorUtilities.bind(sharedServiceLocator, new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(assistantModule.assistant).to(Assistant.class);
+ }
+ });
+
ServiceLocatorUtilities.getService(
sharedServiceLocator, SearchService.class.getName());
ServiceLocatorUtilities.getService(
@@ -361,6 +382,9 @@ public void shutdown(int exitCode) {
if (sharedServiceLocator != null) {
// Stop after Jetty so no new connection can restart the heartbeat scheduler.
sharedServiceLocator.getService(NotebookServer.class).stopHeartbeatScheduler();
+ if (assistantModule != null) {
+ assistantModule.close();
+ }
if (!zConf.isRecoveryEnabled()) {
sharedServiceLocator.getService(InterpreterSettingManager.class).close();
}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/ConfigurationService.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/ConfigurationService.java
index a3b226770d6..001f01e8c94 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/service/ConfigurationService.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/ConfigurationService.java
@@ -37,6 +37,8 @@ public Map getAllProperties(ServiceContext context,
throws IOException {
Map properties = zConf.dumpConfigurations(key ->
!key.contains("password") &&
+ !key.equals(ZeppelinConfiguration.ConfVars
+ .ZEPPELIN_ASSISTANT_OPENAI_API_KEY.getVarName()) &&
!key.equals(ZeppelinConfiguration.ConfVars
.ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING.getVarName()));
callback.onSuccess(properties, context);
@@ -53,6 +55,8 @@ public Map getPropertiesWithPrefix(String prefix,
throws IOException {
Map properties = zConf.dumpConfigurations(key ->
!key.contains("password") &&
+ !key.equals(ZeppelinConfiguration.ConfVars
+ .ZEPPELIN_ASSISTANT_OPENAI_API_KEY.getVarName()) &&
!key.equals(ZeppelinConfiguration.ConfVars
.ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING
.getVarName()) &&
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/Assistant.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/Assistant.java
new file mode 100644
index 00000000000..17f0d9e3cc9
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/Assistant.java
@@ -0,0 +1,78 @@
+/*
+ * 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.zeppelin.service.assistant;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.apache.zeppelin.user.AuthenticationInfo;
+
+public interface Assistant {
+
+ List listConversations(
+ String noteId,
+ Set userAndRoles
+ ) throws IOException;
+
+ Conversation createConversation(
+ String noteId,
+ String title,
+ AuthenticationInfo authInfo,
+ Set userAndRoles
+ ) throws IOException;
+
+ Conversation getConversation(
+ String noteId,
+ String conversationId,
+ Set userAndRoles
+ ) throws IOException;
+
+ Conversation updateTitle(
+ String noteId,
+ String conversationId,
+ String title,
+ String userId,
+ Set userAndRoles
+ ) throws IOException;
+
+ void deleteConversation(
+ String noteId,
+ String conversationId,
+ String userId,
+ Set userAndRoles
+ ) throws IOException;
+
+ MessagePage listMessages(
+ String noteId,
+ String conversationId,
+ String cursor,
+ int limit,
+ Set userAndRoles
+ ) throws IOException;
+
+ Future> sendMessage(
+ String noteId,
+ String conversationId,
+ String userContent,
+ AuthenticationInfo authInfo,
+ Set userAndRoles,
+ AssistantEventListener sink
+ );
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/AssistantEvent.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/AssistantEvent.java
new file mode 100644
index 00000000000..440fc241e52
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/assistant/AssistantEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.zeppelin.service.assistant;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
+public interface AssistantEvent {
+
+ final class TextDelta implements AssistantEvent {
+ public final String delta;
+
+ public TextDelta(String delta) {
+ this.delta = delta;
+ }
+ }
+
+ final class ToolCall implements AssistantEvent {
+ private static final Gson GSON = new Gson();
+
+ public final String id;
+ public final String name;
+ public final Map arguments;
+
+ public ToolCall(String id, String name, String arguments) {
+ this.id = id;
+ this.name = name;
+ this.arguments = StringUtils.isBlank(arguments)
+ ? Collections.emptyMap()
+ : GSON.fromJson(arguments, new TypeToken