diff --git a/README.md b/README.md index 0e0813f..ee212a5 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ import ballerinax/ai.weaviate; ```ballerina ai:VectorStore vectorStore = check new weaviate:VectorStore( - serviceUrl = "add-weaviate-service-url", - config = { - className: "add-collection-name" - }, - auth = { - token: "add-access-token" - } + serviceUrl = "add-weaviate-service-url", + config = { + collectionName: "add-collection-name" + }, + auth = { + token: "add-access-token" + } ); ``` @@ -80,19 +80,26 @@ ai:VectorStore vectorStore = check new weaviate:VectorStore( ```ballerina ai:Error? result = vectorStore.add( - [ - { - id: uuid:createRandomUuid(), - embedding: [1.0, 2.0, 3.0], - chunk: { - 'type: "text", - content: "This is a chunk" - } - } - ] + [ + { + id: uuid:createRandomUuid(), + embedding: [1.0, 2.0, 3.0], + chunk: { + 'type: "text", + content: "This is a chunk" + } + } + ] ); ``` +## Examples + +The Ballerina Weaviate vector store module provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples). + +1. [Book Recommendation System](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples/book-recommendation-system) + This example shows how to use Weaviate vector store APIs to implement a book recommendation system that stores book embeddings and queries them to find similar books based on vector similarity and metadata filtering. + ## Issues and projects Issues and Projects tabs are disabled for this repository as this is part of the Ballerina Library. To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina Library parent repository](https://github.com/ballerina-platform/ballerina-standard-library). diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f4d924f..cff9a3e 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.12.0" [[package]] org = "ballerina" name = "ai" -version = "1.1.4" +version = "1.5.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "data.jsondata"}, @@ -87,7 +87,7 @@ dependencies = [ [[package]] org = "ballerina" name = "data.xmldata" -version = "1.4.2" +version = "1.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.object"} @@ -241,6 +241,14 @@ dependencies = [ {org = "ballerina", name = "lang.regexp"} ] +[[package]] +org = "ballerina" +name = "lang.transaction" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "lang.value" @@ -391,6 +399,27 @@ dependencies = [ {org = "ballerina", name = "observe"} ] +[[package]] +org = "ballerinai" +name = "transaction" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.transaction"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "uuid"} +] +modules = [ + {org = "ballerinai", packageName = "transaction", moduleName = "transaction"} +] + [[package]] org = "ballerinax" name = "ai.weaviate" @@ -400,6 +429,7 @@ dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "uuid"}, + {org = "ballerinai", name = "transaction"}, {org = "ballerinax", name = "weaviate"} ] modules = [ diff --git a/ballerina/README.md b/ballerina/README.md index 1d8057d..6a4d1bf 100644 --- a/ballerina/README.md +++ b/ballerina/README.md @@ -63,7 +63,7 @@ import ballerinax/ai.weaviate; ai:VectorStore vectorStore = check new weaviate:VectorStore( serviceUrl = "add-weaviate-service-url", config = { - className: "add-collection-name" + collectionName: "add-collection-name" }, auth = { token: "add-access-token" @@ -87,3 +87,10 @@ ai:Error? result = vectorStore.add( ] ); ``` + +## Examples + +The Ballerina Weaviate vector store module provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples). + +1. [Book Recommendation System](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples/book-recommendation-system) + This example shows how to use Weaviate vector store APIs to implement a book recommendation system that stores book embeddings and queries them to find similar books based on vector similarity and metadata filtering. diff --git a/ballerina/tests/mock_weaviate_service.bal b/ballerina/tests/mock_weaviate_service.bal deleted file mode 100644 index 67621a2..0000000 --- a/ballerina/tests/mock_weaviate_service.bal +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2025 WSO2 LLC (http://www.wso2.com). -// -// WSO2 LLC. 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. - -import ballerina/http; -import ballerinax/weaviate; - -listener http:Listener weaviateListener = check new http:Listener(9090); - -service / on weaviateListener { - resource function delete objects/[string path]/[string id]() returns error? { - if id != "mock-id" { - return error("This is an error"); - } - } - - resource function post graphql(weaviate:GraphQLQuery payload) returns weaviate:GraphQLResponse|error { - return { - data: { - "Get": { - "Chunk": [ - { - _additional: { - certainty: 0.9999999403953552, - id: "e7d58e49-4ac6-45ca-a921-e6de941b4d99", - vector: [1, 2, 3] - }, - content: "This is a test chunk" - }, - { - _additional: { - certainty: 0.9999999403953552, - id: "121a8352-4cd6-4aea-8897-49c8357682cb", - vector: [1, 2, 3] - }, - content: "This is a test chunk" - } - ] - } - } - }; - } - - resource function post batch/objects(weaviate:Batch_objects_body payload, - string? consistency_level = ()) returns weaviate:ObjectsGetResponse[]|error { - return [ - { - deprecations: null, - result: { - status: "SUCCESS" - }, - 'class: "Chunk", - properties: { - "content": "This is a test chunk", - "type": "text" - }, - id: "mock-id", - creationTimeUnix: 1754499912863, - lastUpdateTimeUnix: 1754499912863, - vector: [1.0, 2.0, 3.0] - } - ]; - } -}; diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index c4ee5fa..c6c869a 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -28,11 +28,13 @@ final VectorStore mockVectorStore = check new ( } ); +string id = uuid:createRandomUuid(); + @test:Config {} function testAddingValuesToVectorStore() returns error? { ai:VectorEntry[] entries = [ { - id: uuid:createRandomUuid(), + id, embedding: [1.0, 2.0, 3.0], chunk: { 'type: "text", @@ -45,15 +47,32 @@ function testAddingValuesToVectorStore() returns error? { } @test:Config {} -function testDeleteValuesFromVectorStore() returns error? { - ai:Error? result = mockVectorStore.delete("mock-id"); +function testDeleteValueFromVectorStore() returns error? { + ai:Error? result = mockVectorStore.delete(id); + test:assertTrue(result !is error); +} + +@test:Config {} +function testDeleteMultipleValuesFromVectorStore() returns error? { + string index = uuid:createRandomUuid(); + ai:VectorEntry[] entries = [ + { + id: index, + embedding: [1.0, 2.0, 3.0], + chunk: { + 'type: "text", + content: "This is a test chunk" + } + } + ]; + _ = check mockVectorStore.add(entries); + ai:Error? result = mockVectorStore.delete([id, index]); test:assertTrue(result !is error); } @test:Config {} function testQueryValuesFromVectorStore() returns error? { ai:VectorStoreQuery query = { - embedding: [1.0, 2.0, 3.0], filters: { filters: [ { @@ -67,3 +86,48 @@ function testQueryValuesFromVectorStore() returns error? { ai:VectorMatch[]|ai:Error result = mockVectorStore.query(query); test:assertTrue(result !is error); } + +@test:Config {} +function testVectorStoreInitializationWithInvalidURL() returns error? { + VectorStore store = check new ( + serviceUrl = "invalid-url", + config = { + collectionName: "TestChunk" + }, + auth = { + token: "test-token" + } + ); + ai:VectorMatch[]|ai:Error result = store.query({ + topK: 0, + embedding: [1.0, 2.0, 3.0] + }); + test:assertTrue(result is ai:Error); +} + +@test:Config {} +function testAddEmptyVectorEntriesArray() returns error? { + ai:VectorEntry[] emptyEntries = []; + ai:Error? result = mockVectorStore.add(emptyEntries); + test:assertTrue(result is error, ""); +} + +@test:Config {} +function testQueryWithTopKZero() returns error? { + ai:VectorStoreQuery query = { + topK: 0, + embedding: [1.0, 2.0, 3.0] + }; + ai:VectorMatch[]|ai:Error result = mockVectorStore.query(query); + test:assertTrue(result is error); +} + +@test:Config {} +function testQueryWithNegativeTopK() returns error? { + ai:VectorStoreQuery query = { + topK: -5, + embedding: [1.0, 2.0, 3.0] + }; + ai:VectorMatch[]|ai:Error result = mockVectorStore.query(query); + test:assertTrue(result is error); +} diff --git a/ballerina/types.bal b/ballerina/types.bal index ed71265..98566c2 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -19,17 +19,15 @@ # # + collectionName - The name of the collection to use # + chunkFieldName - The name of the field to contain the chunk details -# + topK - The number of top similar vectors to return in queries public type Configuration record {| string collectionName; string chunkFieldName?; - int topK = 10; |}; type QueryResult record { record { string? id; - float certainty; + float? certainty; float[] vector; } _additional; string content?; diff --git a/ballerina/vector_store.bal b/ballerina/vector_store.bal index 0ce54d4..69f8f15 100644 --- a/ballerina/vector_store.bal +++ b/ballerina/vector_store.bal @@ -47,7 +47,6 @@ public isolated class VectorStore { } self.weaviateClient = weaviateClient; self.config = config.cloneReadOnly(); - self.topK = self.config.topK; lock { string? chunkFieldName = self.config.cloneReadOnly().chunkFieldName; self.chunkFieldName = chunkFieldName is () ? "content" : chunkFieldName; @@ -60,47 +59,57 @@ public isolated class VectorStore { # + return - An `ai:Error` if the addition fails, otherwise returns `()` public isolated function add(ai:VectorEntry[] entries) returns ai:Error? { if entries.length() == 0 { - return; + return error("No entries provided to add to the vector store"); } lock { weaviate:Object[] objects = []; foreach ai:VectorEntry entry in entries.cloneReadOnly() { ai:Embedding embedding = entry.embedding; + weaviate:PropertySchema properties = entry.chunk.metadata !is () ? + check entry.chunk.metadata.cloneWithType() : {}; + properties[self.chunkFieldName] = entry.chunk.content; + properties["type"] = entry.chunk.'type; + if embedding is ai:Vector { objects.push({ 'class: self.config.collectionName, id: entry.id, vector: embedding, - properties: { - "type": entry.chunk.'type, - [self.chunkFieldName]: entry.chunk.content - } + properties }); } // TODO: Add support for sparse and hybrid embeddings // Weaviate does not support custom sparse or hybrid embeddings directly // Need to convert them to dense vectors before adding to Weaviate } - weaviate:ObjectsGetResponse[]|error result = self.weaviateClient->/batch/objects.post({ + weaviate:ObjectsGetResponse[] _ = check self.weaviateClient->/batch/objects.post({ objects }); - if result is error { - return error("Failed to add vector entries", result); - } + } on fail error err { + return error("failed to add entries to the vector store", err); } } # Deletes a vector entry from the Weaviate vector store. # - # + id - The ID of the vector entry to delete + # + ids - One or more identifiers of the vector entries to delete # + return - An `ai:Error` if the deletion fails, otherwise returns `()` - public isolated function delete(string id) returns ai:Error? { + public isolated function delete(string|string[] ids) returns ai:Error? { lock { string path = self.config.collectionName; - http:Response|error result = self.weaviateClient->/objects/[path]/[id].delete(); - if result is error { - return error("Failed to query vector store", result); + if ids is string[] { + transaction { + foreach string id in ids.cloneReadOnly() { + _ = check deleteById(id, path, self.weaviateClient); + } + error? commitResult = commit; + if commitResult is error { + return error("failed to delete vector entries", commitResult); + } + } + return; } + return deleteById(ids, path, self.weaviateClient); } } @@ -111,6 +120,9 @@ public isolated class VectorStore { public isolated function query(ai:VectorStoreQuery query) returns ai:VectorMatch[]|ai:Error { ai:VectorMatch[] finalMatches; lock { + if query.topK == 0 || query.topK < -1 { + return error("Invalid value for topK. The value cannot be 0 or less than -1."); + } string filterSection = ""; if query.hasKey("filters") && query.filters is ai:MetadataFilters { ai:MetadataFilters? filters = query.cloneReadOnly().filters; @@ -122,10 +134,12 @@ public isolated class VectorStore { string gqlQuery = string `{ Get { ${self.config.collectionName}( - limit: ${self.topK} + ${query.topK > -1 ? string `limit: ${query.topK}` : string ``} ${filterSection} - nearVector: { - vector: ${query.embedding.toJsonString()} + ${query.embedding !is () ? + string `nearVector: { + vector: ${query.embedding.toJsonString()} + }` : string `` } ) { content @@ -163,15 +177,23 @@ public isolated class VectorStore { 'type: element.'type is () ? "" : check element.'type.cloneWithType(), content: element.content }, - similarityScore: element._additional.certainty + similarityScore: element._additional.certainty !is () ? + check element._additional.certainty.cloneWithType() : 0.0 }); - } on fail error err { - return error("Failed to parse vector store query", err); - } + } finalMatches = matches.cloneReadOnly(); } on fail error err { - return error("Failed to query vector store", err); + return error("failed to query vector store", err); } return finalMatches; } } + +isolated function deleteById(string id, string path, weaviate:Client weaviateClient) returns ai:Error? { + lock { + http:Response|error result = weaviateClient->/objects/[path]/[id].delete(); + if result is error { + return error("failed to delete entry from the vector store", result); + } + } +} diff --git a/build.gradle b/build.gradle index 6587c2b..f24e791 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,7 @@ def moduleVersion = project.version.replace("-SNAPSHOT", "") task build { dependsOn(':ai.weaviate-ballerina:build') + dependsOn(':ai.weaviate-examples:build') } release { diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..57175ee --- /dev/null +++ b/examples/README.md @@ -0,0 +1,52 @@ +## Examples + +The Ballerina Weaviate vector store module provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples). + +1. [Book Recommendation System](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate/tree/main/examples/book-recommendation-system) + This example shows how to use Weaviate vector store APIs to implement a book recommendation system that stores book embeddings and queries them to find similar books based on vector similarity and metadata filtering. + +## Prerequisites + +1. Follow the [instructions](https://github.com/ballerina-platform/module-ballerinax-ai.weaviate#set-up-guide) to set up the Weaviate cluster and obtain API credentials. + +2. For each example, create a `Config.toml` file with your Weaviate service URL, collection name, and API token. Here's an example of how your `Config.toml` file should look: + + ```toml + serviceUrl = "" + collectionName = "" + token = "" + ``` + +## Running an Example + +Execute the following commands to build an example from the source: + +* To build an example: + + ```bash + bal build + ``` + +* To run an example: + + ```bash + bal run + ``` + +## Building the Examples with the Local Module + +**Warning**: Due to the absence of support for reading local repositories for single Ballerina files, the Bala of the module is manually written to the central repository as a workaround. Consequently, the bash script may modify your local Ballerina repositories. + +Execute the following commands to build all the examples against the changes you have made to the module locally: + +* To build all the examples: + + ```bash + ./build.sh build + ``` + +* To run all the examples: + + ```bash + ./build.sh run + ``` diff --git a/examples/book-recommendation-system/.github/README.md b/examples/book-recommendation-system/.github/README.md new file mode 120000 index 0000000..73de6ce --- /dev/null +++ b/examples/book-recommendation-system/.github/README.md @@ -0,0 +1 @@ +../book-recommendation-system.md \ No newline at end of file diff --git a/examples/book-recommendation-system/Ballerina.toml b/examples/book-recommendation-system/Ballerina.toml new file mode 100644 index 0000000..84244c6 --- /dev/null +++ b/examples/book-recommendation-system/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "book_recommendation_system" +version = "0.1.0" +distribution = "2201.12.0" + +[build-options] +observabilityIncluded = true diff --git a/examples/book-recommendation-system/book-recommendation-system.md b/examples/book-recommendation-system/book-recommendation-system.md new file mode 100644 index 0000000..eaad3ea --- /dev/null +++ b/examples/book-recommendation-system/book-recommendation-system.md @@ -0,0 +1,107 @@ +# Book recommendation system with Weaviate vector store + +This example demonstrates the use of the Ballerina Weaviate vector store module for building a book recommendation system. The system stores book embeddings and queries them to find similar books based on vector similarity and metadata filtering. + +## Step 1: Import the modules + +Import the required modules for AI operations, I/O operations, UUID generation, and Weaviate vector store. + +```ballerina +import ballerina/ai; +import ballerina/io; +import ballerina/uuid; +import ballerinax/ai.weaviate; +``` + +## Step 2: Configure the application + +Set up configurable variables for Weaviate connection parameters. + +```ballerina +configurable string serviceUrl = ?; +configurable string collectionName = ?; +configurable string token = ?; +``` + +## Step 3: Create a vector store instance + +Initialize the Weaviate vector store with your service URL, collection name, and authentication token. + +```ballerina +weaviate:VectorStore vectorStore = check new (serviceUrl, { + collectionName +}, { + auth: { + token + } +}); +``` + +Now, the `weaviate:VectorStore` instance can be used for storing and querying book embeddings. + +## Step 4: Add book embeddings to the vector store + +Store book information with their vector embeddings and metadata in the Weaviate vector store. + +```ballerina +// This is the embedding of a sample book entry +ai:Vector bookEmbedding = [0.1, 0.2, 0.3]; + +ai:Error? addResult = vectorStore.add([ + { + id: uuid:createRandomUuid(), + embedding: bookEmbedding, + chunk: { + 'type: "text", + content: "A Game of Thrones", + metadata: { + "genre": "Fantasy" + } + } + } +]); + +if addResult is ai:Error { + io:println("Error occurred while adding an entry to the vector store", addResult); + return; +} +``` + +## Step 5: Query for book recommendations + +Search for similar books using vector similarity and apply metadata filters to refine the results. + +```ballerina +// This is the embedding of the search query. It should use the same model as the embedding of the book entries. +ai:Vector searchEmbedding = [0.05, 0.1, 0.15]; + +ai:VectorMatch[] query = check vectorStore.query({ + embedding: searchEmbedding, + filters: { + filters: [ + { + 'key: "genre", + operator: ai:EQUAL, + value: "Fantasy" + } + ] + } +}); +io:println("Query Results: ", query); +``` + +## Step 6: Understanding the results + +The query results contain book recommendations with similarity scores and metadata. Each result includes: + +- **id**: Unique identifier for the book entry +- **embedding**: The vector representation of the book +- **chunk**: Contains the book information including type, content, and metadata +- **similarityScore**: How similar the book is to your search query (higher scores indicate better matches) + +The system can be extended to, + +- Add more books with richer metadata (author, publication year, ISBN, etc.) +- Use real embeddings from language models instead of sample vectors +- Implement more complex filtering logic +- Build a REST API around the recommendation system diff --git a/examples/book-recommendation-system/main.bal b/examples/book-recommendation-system/main.bal new file mode 100644 index 0000000..6834a75 --- /dev/null +++ b/examples/book-recommendation-system/main.bal @@ -0,0 +1,93 @@ +// Copyright (c) 2025 WSO2 LLC (http://www.wso2.com). +// +// WSO2 LLC. 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. + +import ballerina/ai; +import ballerina/io; +import ballerina/uuid; +import ballerinax/ai.weaviate; + +configurable string serviceUrl = ?; +configurable string collectionName = ?; +configurable string token = ?; + +type BookEntry record { + float[] embedding; + string title; + string genre; +}; + +public function main() returns error? { + weaviate:VectorStore vectorStore = check new (serviceUrl, { + collectionName + }, { + auth: { + token + } + }); + + BookEntry[] entries = [ + { + title: "A Game of Thrones", + genre: "Fantasy", + embedding: [0.1011, 0.20012, 0.3024] + }, + { + title: "Crime And Punishment", + genre: "Literary fiction", + embedding: [0.98543, 0.347843, 0.845395] + }, + { + title: "1984", + genre: "Science fiction", + embedding: [0.5645, 0.574, 0.3384] + } + ]; + + ai:Error? addResult = vectorStore.add(from BookEntry entry in entries + select { + id: uuid:createRandomUuid(), + embedding: entry.embedding, + chunk: { + 'type: "text", + content: entry.title, + metadata: { + "genre": entry.genre + } + } + } + ); + if addResult is ai:Error { + io:println("Error occurred while adding an entry to the vector store", addResult); + return; + } + + // This is the embedding of the search query. It should use the same model as the embedding of the book entries. + ai:Vector searchEmbedding = [0.05, 0.1, 0.15]; + + ai:VectorMatch[] query = check vectorStore.query({ + embedding: searchEmbedding, + filters: { + filters: [ + { + 'key: "genre", + operator: ai:EQUAL, + value: "Fantasy" + } + ] + } + }); + io:println("Query Results: ", query); +} diff --git a/examples/build.gradle b/examples/build.gradle new file mode 100644 index 0000000..d592abf --- /dev/null +++ b/examples/build.gradle @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. 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. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +apply plugin: 'java' + +def graalvmFlag = "" + +task testExamples { + if (project.hasProperty("balGraalVMTest")) { + graalvmFlag = "--graalvm" + } + doLast { + try { + exec { + workingDir project.projectDir + println("Working dir: ${workingDir}") + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'sh', "/c", "chmod +x ./build.sh && ./build.sh run && exit %%ERRORLEVEL%%" + } else { + commandLine 'sh', "-c", "chmod +x ./build.sh && ./build.sh run" + } + } + } catch (Exception e) { + println("Example Build failed: " + e.message) + throw e + } + } +} + +task buildExamples { + gradle.taskGraph.whenReady { graph -> + if (graph.hasTask(":ai.weaviate-ballerina:test")) { + buildExamples.enabled = false + } else { + testExamples.enabled = false + } + } + doLast { + try { + exec { + workingDir project.projectDir + println("Working dir: ${workingDir}") + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'sh', "/c", "chmod +x ./build.sh && ./build.sh build && exit %%ERRORLEVEL%%" + } else { + commandLine 'sh', "-c", "chmod +x ./build.sh && ./build.sh build" + } + } + } catch (Exception e) { + println("Example Build failed: " + e.message) + throw e + } + } +} + +buildExamples.dependsOn ":ai.weaviate-ballerina:build" +testExamples.dependsOn ":ai.weaviate-ballerina:build" + +build.dependsOn buildExamples diff --git a/examples/build.sh b/examples/build.sh new file mode 100755 index 0000000..e2659d3 --- /dev/null +++ b/examples/build.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +BAL_EXAMPLES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BAL_CENTRAL_DIR="$HOME/.ballerina/repositories/central.ballerina.io/" +BAL_HOME_DIR="$BAL_EXAMPLES_DIR/../ballerina" + +set -e + +case "$1" in +build) + BAL_CMD="build" + ;; +run) + BAL_CMD="run" + ;; +*) + echo "Invalid command provided: '$1'. Please provide 'build' or 'run' as the command." + exit 1 + ;; +esac + +# Read Ballerina package name +BAL_PACKAGE_NAME=$(awk -F'"' '/^name/ {print $2}' "$BAL_HOME_DIR/Ballerina.toml") + +# Push the package to the local repository +cd "$BAL_HOME_DIR" && + bal pack && + bal push --repository=local + +# Remove the cache directories in the repositories +rm -rf "$BAL_CENTRAL_DIR/cache-*" + +# Update the central repository +BAL_DESTINATION_DIR="$HOME/.ballerina/repositories/central.ballerina.io/bala/ballerinax" +BAL_SOURCE_DIR="$HOME/.ballerina/repositories/local/bala/ballerinax/$BAL_PACKAGE_NAME" + +mkdir -p "$BAL_DESTINATION_DIR" + +[ -d "$BAL_SOURCE_DIR" ] && cp -r "$BAL_SOURCE_DIR" "$BAL_DESTINATION_DIR" +echo "Successfully updated the local central repositories" + +# Loop through examples in the examples directory +cd "$BAL_EXAMPLES_DIR" +for dir in $(find "$BAL_EXAMPLES_DIR" -type d -maxdepth 1 -mindepth 1); do + # Skip the build directory + if [[ "$dir" == *build ]]; then + continue + fi + (cd "$dir" && bal "$BAL_CMD" --offline && cd ..); +done + +# Remove generated JAR files +find "$BAL_HOME_DIR" -maxdepth 1 -type f -name "*.jar" | while read -r JAR_FILE; do + rm "$JAR_FILE" +done diff --git a/settings.gradle b/settings.gradle index 9a3be6b..164b737 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,8 +33,10 @@ plugins { rootProject.name = 'module-balerinax-ai.weaviate' include ':ai.weaviate-ballerina' +include ':ai.weaviate-examples' project(':ai.weaviate-ballerina').projectDir = file("ballerina") +project(':ai.weaviate-examples').projectDir = file("examples") gradleEnterprise { buildScan {