diff --git a/build.gradle b/build.gradle index 7e9ecef..7f23b50 100644 --- a/build.gradle +++ b/build.gradle @@ -120,7 +120,11 @@ dependencies { } tasks.withType(Test).configureEach { - useJUnitPlatform() + useJUnitPlatform { + if (System.getenv('CI')) { + excludeTags 'excludeFromCI' + } + } } swaggerSources { diff --git a/src/main/java/org/typesense/api/Client.java b/src/main/java/org/typesense/api/Client.java index 84e94f0..6c2bccf 100644 --- a/src/main/java/org/typesense/api/Client.java +++ b/src/main/java/org/typesense/api/Client.java @@ -24,6 +24,9 @@ public class Client { private Stopwords stopwords; private Map individualStopwordsSets; + private Conversations conversations; + private Map individualConversations; + public Health health; public Operations operations; public Metrics metrics; @@ -47,6 +50,8 @@ public Client(Configuration configuration){ this.analytics = new Analytics(this.apiCall); this.stopwords = new Stopwords(this.apiCall); this.individualStopwordsSets = new HashMap<>(); + this.conversations = new Conversations(this.apiCall); + this.individualConversations = new HashMap<>(); } public Collection collections(String name){ @@ -114,4 +119,19 @@ public StopwordsSet stopwords(String stopwordsSetId) { retVal = this.individualStopwordsSets.get(stopwordsSetId); return retVal; } + + public Conversation conversations(String id) { + Conversation retVal; + + if (!this.individualConversations.containsKey(id)) { + this.individualConversations.put(id, new Conversation(id, this.apiCall)); + } + + retVal = this.individualConversations.get(id); + return retVal; + } + + public Conversations conversations() { + return this.conversations; + } } diff --git a/src/main/java/org/typesense/api/Conversation.java b/src/main/java/org/typesense/api/Conversation.java new file mode 100644 index 0000000..8b7d106 --- /dev/null +++ b/src/main/java/org/typesense/api/Conversation.java @@ -0,0 +1,35 @@ + +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.interfaces.ConversationDeleteSchema; +import org.typesense.interfaces.ConversationSchema; +import org.typesense.model.CollectionUpdateSchema; + +public class Conversation { + private final ApiCall apiCall; + private final String conversationId; + + public Conversation(String conversationId, ApiCall apiCall) { + this.apiCall = apiCall; + this.conversationId = conversationId; + } + + + public ConversationSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, ConversationSchema.class); + } + + public ConversationDeleteSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, ConversationDeleteSchema.class); + } + + public CollectionUpdateSchema update(CollectionUpdateSchema schema) throws Exception { + return this.apiCall.put(this.getEndpoint(), schema, null, CollectionUpdateSchema.class); + } + + private String getEndpoint() { + return Conversations.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(conversationId); + } + +} diff --git a/src/main/java/org/typesense/api/ConversationModel.java b/src/main/java/org/typesense/api/ConversationModel.java new file mode 100644 index 0000000..893004e --- /dev/null +++ b/src/main/java/org/typesense/api/ConversationModel.java @@ -0,0 +1,34 @@ + +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.ConversationModelSchema; +import org.typesense.model.ConversationModelUpdateSchema; + +public class ConversationModel { + + private final ApiCall apiCall; + private final String id; + + public ConversationModel(ApiCall apiCall, String id) { + this.apiCall = apiCall; + this.id = id; + } + + public ConversationModelUpdateSchema update(ConversationModelUpdateSchema schema) throws Exception { + return this.apiCall.put(this.getEndpoint(), schema, null, ConversationModelUpdateSchema.class); + } + + public ConversationModelSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, ConversationModelSchema.class); + } + + public ConversationModelSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, ConversationModelSchema.class); + } + + private String getEndpoint() { + return ConversationModels.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(this.id); + } + +} diff --git a/src/main/java/org/typesense/api/ConversationModels.java b/src/main/java/org/typesense/api/ConversationModels.java new file mode 100644 index 0000000..0a81398 --- /dev/null +++ b/src/main/java/org/typesense/api/ConversationModels.java @@ -0,0 +1,23 @@ +package org.typesense.api; + +import org.typesense.model.ConversationModelCreateSchema; +import org.typesense.model.ConversationModelSchema; + +public class ConversationModels { + + private final ApiCall apiCall; + public final static String RESOURCE_PATH = "/conversations/models"; + + public ConversationModels(ApiCall apiCall) { + this.apiCall = apiCall; + } + + public ConversationModelCreateSchema create(ConversationModelCreateSchema schema) throws Exception { + return this.apiCall.post(RESOURCE_PATH, schema, null, ConversationModelCreateSchema.class); + } + + public ConversationModelSchema[] retrieve() throws Exception { + return this.apiCall.get(RESOURCE_PATH, null, ConversationModelSchema[].class); + } + +} diff --git a/src/main/java/org/typesense/api/Conversations.java b/src/main/java/org/typesense/api/Conversations.java new file mode 100644 index 0000000..58368c1 --- /dev/null +++ b/src/main/java/org/typesense/api/Conversations.java @@ -0,0 +1,40 @@ +package org.typesense.api; + +import java.util.HashMap; +import java.util.Map; + +import org.typesense.interfaces.ConversationsRetrieveSchema; + +public class Conversations { + + private final ApiCall apiCall; + public final static String RESOURCE_PATH = "/conversations"; + + private final ConversationModels conversationModels; + private final Map individualConversations; + + public Conversations(ApiCall apiCall) { + this.apiCall = apiCall; + this.conversationModels = new ConversationModels(this.apiCall); + this.individualConversations = new HashMap<>(); + } + + public ConversationsRetrieveSchema retrieve() throws Exception { + return this.apiCall.get(Conversations.RESOURCE_PATH, null, ConversationsRetrieveSchema.class); + } + + public ConversationModels models() { + return this.conversationModels; + } + + public ConversationModel models(String conversationId) { + ConversationModel retVal; + + if (!this.individualConversations.containsKey(conversationId)) { + this.individualConversations.put(conversationId, new ConversationModel(apiCall, conversationId)); + } + + retVal = this.individualConversations.get(conversationId); + return retVal; + } +} diff --git a/src/main/java/org/typesense/interfaces/ConversationDeleteSchema.java b/src/main/java/org/typesense/interfaces/ConversationDeleteSchema.java new file mode 100644 index 0000000..fe52e5b --- /dev/null +++ b/src/main/java/org/typesense/interfaces/ConversationDeleteSchema.java @@ -0,0 +1,57 @@ +package org.typesense.interfaces; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ConversationDeleteSchema { + @JsonProperty("id") + private Long id = null; + + public ConversationDeleteSchema id(Long id) { + this.id = id; + return this; + } + + /** + * The ID of the conversation to delete + * @return id + **/ + @Schema(required = true, description = "The ID of the conversation to delete") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationDeleteSchema that = (ConversationDeleteSchema) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConversationDeleteSchema {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString(java.lang.Object o) { + if (o == null) return "null"; + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/org/typesense/interfaces/ConversationSchema.java b/src/main/java/org/typesense/interfaces/ConversationSchema.java new file mode 100644 index 0000000..173efb0 --- /dev/null +++ b/src/main/java/org/typesense/interfaces/ConversationSchema.java @@ -0,0 +1,128 @@ +package org.typesense.interfaces; + + +import java.util.Arrays; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ConversationSchema { + @JsonProperty("id") + private Long id = null; + + @JsonProperty("conversation") + private Object[] conversation = null; + + @JsonProperty("last_updated") + private Long lastUpdated = null; + + @JsonProperty("ttl") + private Long ttl = null; + + public ConversationSchema id(Long id) { + this.id = id; + return this; + } + + /** + * The unique identifier of the conversation + * @return id + **/ + @Schema(required = true, description = "The unique identifier of the conversation") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ConversationSchema conversation(Object[] conversation) { + this.conversation = conversation; + return this; + } + + /** + * Array of conversation objects + * @return conversation + **/ + @Schema(required = true, description = "Array of conversation objects") + public Object[] getConversation() { + return conversation; + } + + public void setConversation(Object[] conversation) { + this.conversation = conversation; + } + + public ConversationSchema lastUpdated(Long lastUpdated) { + this.lastUpdated = lastUpdated; + return this; + } + + /** + * Timestamp of when the conversation was last updated + * @return lastUpdated + **/ + @Schema(required = true, description = "Timestamp of when the conversation was last updated") + public Long getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Long lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public ConversationSchema ttl(Long ttl) { + this.ttl = ttl; + return this; + } + + /** + * Time to live for the conversation in seconds + * @return ttl + **/ + @Schema(required = true, description = "Time to live for the conversation in seconds") + public Long getTtl() { + return ttl; + } + + public void setTtl(Long ttl) { + this.ttl = ttl; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationSchema that = (ConversationSchema) o; + return Objects.equals(this.id, that.id) && + Arrays.equals(this.conversation, that.conversation) && + Objects.equals(this.lastUpdated, that.lastUpdated) && + Objects.equals(this.ttl, that.ttl); + } + + @Override + public int hashCode() { + return Objects.hash(id, Arrays.hashCode(conversation), lastUpdated, ttl); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConversationSchema {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" conversation: ").append(toIndentedString(Arrays.toString(conversation))).append("\n"); + sb.append(" lastUpdated: ").append(toIndentedString(lastUpdated)).append("\n"); + sb.append(" ttl: ").append(toIndentedString(ttl)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString(java.lang.Object o) { + if (o == null) return "null"; + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/org/typesense/interfaces/ConversationUpdateSchema.java b/src/main/java/org/typesense/interfaces/ConversationUpdateSchema.java new file mode 100644 index 0000000..e23f201 --- /dev/null +++ b/src/main/java/org/typesense/interfaces/ConversationUpdateSchema.java @@ -0,0 +1,57 @@ +package org.typesense.interfaces; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ConversationUpdateSchema { + @JsonProperty("ttl") + private Long ttl = null; + + public ConversationUpdateSchema ttl(Long ttl) { + this.ttl = ttl; + return this; + } + + /** + * Time to live for the conversation in seconds + * @return ttl + **/ + @Schema(required = true, description = "Time to live for the conversation in seconds") + public Long getTtl() { + return ttl; + } + + public void setTtl(Long ttl) { + this.ttl = ttl; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationUpdateSchema that = (ConversationUpdateSchema) o; + return Objects.equals(this.ttl, that.ttl); + } + + @Override + public int hashCode() { + return Objects.hash(ttl); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConversationUpdateSchema {\n"); + sb.append(" ttl: ").append(toIndentedString(ttl)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString(java.lang.Object o) { + if (o == null) return "null"; + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/org/typesense/interfaces/ConversationsRetrieveSchema.java b/src/main/java/org/typesense/interfaces/ConversationsRetrieveSchema.java new file mode 100644 index 0000000..fc4fcfe --- /dev/null +++ b/src/main/java/org/typesense/interfaces/ConversationsRetrieveSchema.java @@ -0,0 +1,57 @@ +package org.typesense.interfaces; + +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ConversationsRetrieveSchema { + @JsonProperty("conversations") + private ConversationSchema[] conversations = null; + + public ConversationsRetrieveSchema conversations(ConversationSchema[] conversations) { + this.conversations = conversations; + return this; + } + + /** + * Array of conversations + * @return conversations + **/ + @Schema(required = true, description = "Array of conversations") + public ConversationSchema[] getConversations() { + return conversations; + } + + public void setConversations(ConversationSchema[] conversations) { + this.conversations = conversations; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationsRetrieveSchema that = (ConversationsRetrieveSchema) o; + return Arrays.equals(this.conversations, that.conversations); + } + + @Override + public int hashCode() { + return Arrays.hashCode(conversations); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ConversationsRetrieveSchema {\n"); + sb.append(" conversations: ").append(toIndentedString(Arrays.toString(conversations))).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString(java.lang.Object o) { + if (o == null) return "null"; + return o.toString().replace("\n", "\n "); + } +} \ No newline at end of file diff --git a/src/test/java/org/typesense/api/ConversationModelsTest.java b/src/test/java/org/typesense/api/ConversationModelsTest.java new file mode 100644 index 0000000..7eeafff --- /dev/null +++ b/src/test/java/org/typesense/api/ConversationModelsTest.java @@ -0,0 +1,107 @@ +package org.typesense.api; + +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.typesense.model.ConversationModelCreateSchema; +import org.typesense.model.ConversationModelSchema; +import org.typesense.model.ConversationModelUpdateSchema; + +@Tag("excludeFromCI") +class ConversationModelsTest { + + Client client; + private Helper helper; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + this.client = helper.getClient(); + helper.teardown(); + helper.createConversationCollection(); + } + + @AfterEach + void tearDown() throws Exception { + helper.teardown(); + } + + @Test + void testCreate() throws Exception { + ConversationModelCreateSchema model = new ConversationModelCreateSchema(); + + model.setId("conv-model"); + model.setHistoryCollection("conversations"); + model.setSystemPrompt( + "You are an assistant for question-answering. You can only make conversations based on the provided context. If a response cannot be formed strictly using the provided context, politely say you do not have knowledge about that topic."); + model.setApiKey(System.getenv("OPENAI_API_KEY")); + model.setConversationModelCreateSchemaModelName("openai/gpt-3.5-turbo"); + model.setConversationModelCreateSchemaMaxBytes(16384); + model.setConversationModelCreateSchemaHistoryCollection("conversations"); + + ConversationModelCreateSchema result = client.conversations().models().create(model); + assertNotNull(result); + + assertEquals("conv-model", result.getId()); + assertEquals(16384, result.getConversationModelCreateSchemaMaxBytes()); + assertEquals("openai/gpt-3.5-turbo", result.getConversationModelCreateSchemaModelName()); + assertEquals( + "You are an assistant for question-answering. You can only make conversations based on the provided context. If a response cannot be formed strictly using the provided context, politely say you do not have knowledge about that topic.", + result.getSystemPrompt()); + } + + @Test + void testRetrieveAll() throws Exception { + helper.createTestConversationModel(); + + ConversationModelSchema[] results = client.conversations().models().retrieve(); + + assertNotNull(results); + + ConversationModelSchema model = results[0]; + + assertEquals("conv-model", model.getConversationModelSchemaId()); + assertEquals(16384, model.getConversationModelCreateSchemaMaxBytes()); + assertEquals("openai/gpt-3.5-turbo", model.getConversationModelCreateSchemaModelName()); + assertEquals( + "You are an assistant for question-answering. You can only make conversations based on the provided context. If a response cannot be formed strictly using the provided context, politely say you do not have knowledge about that topic.", + model.getSystemPrompt()); + } + + @Test + void testRetrieve() throws Exception { + helper.createTestConversationModel(); + + ConversationModelSchema model = client.conversations().models("conv-model").retrieve(); + + assertNotNull(model); + + assertEquals("conv-model", model.getConversationModelSchemaId()); + assertEquals(16384, model.getConversationModelCreateSchemaMaxBytes()); + assertEquals("openai/gpt-3.5-turbo", model.getConversationModelCreateSchemaModelName()); + assertEquals( + "You are an assistant for question-answering. You can only make conversations based on the provided context. If a response cannot be formed strictly using the provided context, politely say you do not have knowledge about that topic.", + model.getSystemPrompt()); + } + + @Test + void testUpdate() throws Exception { + helper.createTestConversationModel(); + + ConversationModelUpdateSchema model = new ConversationModelUpdateSchema(); + model.setSystemPrompt("New system prompt"); + + ConversationModelUpdateSchema result = client.conversations().models("conv-model").update(model); + + assertNotNull(result); + + assertEquals("conv-model", result.getId()); + assertEquals(16384, result.getMaxBytes()); + assertEquals("openai/gpt-3.5-turbo", result.getModelName()); + assertEquals("New system prompt", result.getSystemPrompt()); + } + +} diff --git a/src/test/java/org/typesense/api/Helper.java b/src/test/java/org/typesense/api/Helper.java index de910e3..16b2bb0 100644 --- a/src/test/java/org/typesense/api/Helper.java +++ b/src/test/java/org/typesense/api/Helper.java @@ -20,6 +20,8 @@ import org.typesense.model.CollectionAliasesResponse; import org.typesense.model.CollectionResponse; import org.typesense.model.CollectionSchema; +import org.typesense.model.ConversationModelCreateSchema; +import org.typesense.model.ConversationModelSchema; import org.typesense.model.Field; import org.typesense.model.SearchOverrideInclude; import org.typesense.model.SearchOverrideRule; @@ -52,6 +54,34 @@ public void createTestCollection() throws Exception { client.collections().create(collectionSchema); } + public void createConversationCollection() throws Exception { + List fields = new ArrayList<>(); + fields.add(new Field().name("conversation_id").type(FieldTypes.STRING)); + fields.add(new Field().name("model_id").type(FieldTypes.STRING)); + fields.add(new Field().name("timestamp").type(FieldTypes.INT32)); + fields.add(new Field().name("role").type(FieldTypes.STRING).index(false)); + fields.add(new Field().name("message").type(FieldTypes.STRING).index(false)); + + CollectionSchema collectionSchema = new CollectionSchema(); + collectionSchema.name("conversations").fields(fields); + + client.collections().create(collectionSchema); + } + + public void createTestConversationModel() throws Exception { + ConversationModelCreateSchema model = new ConversationModelCreateSchema(); + model.setId("conv-model"); + model.setHistoryCollection("conversations"); + model.setSystemPrompt( + "You are an assistant for question-answering. You can only make conversations based on the provided context. If a response cannot be formed strictly using the provided context, politely say you do not have knowledge about that topic."); + model.setApiKey(System.getenv("OPENAI_API_KEY")); + model.setConversationModelCreateSchemaModelName("openai/gpt-3.5-turbo"); + model.setConversationModelCreateSchemaMaxBytes(16384); + model.setConversationModelCreateSchemaHistoryCollection("conversations"); + + client.conversations().models().create(model); + } + public void createTestQueryCollection() throws Exception { List fields = new ArrayList<>(); fields.add(new Field().name("q").type((FieldTypes.STRING))); @@ -165,5 +195,10 @@ public void teardown() throws Exception { for (StopwordsSetSchema s : stopwords.getStopwords()) { client.stopwords(s.getId()).delete(); } + + ConversationModelSchema[] models = client.conversations().models().retrieve(); + for (ConversationModelSchema s : models) { + client.conversations().models(s.getConversationModelSchemaId()).delete(); + } } }