From b95f4bc0bfb149a1a5bc8cb46ee47758b93a73df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Cayuelas=20Ruiz?= Date: Thu, 9 Nov 2023 11:28:02 +0100 Subject: [PATCH] Remove support for Java and Scala --- .../workflows/publish-development-version.yml | 36 +--- .github/workflows/publish.yml | 36 +--- README.md | 44 +--- .../ScalaPublishingConventionsPlugin.kt | 60 ------ ...ef-scala-publishing-conventions.gradle.kts | 1 - core/TECHNICAL.MD | 8 +- docs/intro/java.md | 193 ------------------ docs/intro/scala.md | 146 ------------- examples/java/build.gradle.kts | 16 -- .../auto/jdk21/contexts/BreakingNews.java | 36 ---- .../auto/jdk21/contexts/DivergentTasks.java | 28 --- .../xef/java/auto/jdk21/contexts/Markets.java | 36 ---- .../java/auto/jdk21/contexts/PDFDocument.java | 48 ----- .../xef/java/auto/jdk21/contexts/Weather.java | 30 --- .../auto/jdk21/conversations/Animals.java | 50 ----- .../xef/java/auto/jdk21/gpt4all/Chat.java | 79 ------- .../auto/jdk21/serialization/ASCIIArt.java | 19 -- .../java/auto/jdk21/serialization/Book.java | 23 --- .../java/auto/jdk21/serialization/Books.java | 33 --- .../auto/jdk21/serialization/ChessAI.java | 62 ------ .../java/auto/jdk21/serialization/Movies.java | 20 -- .../auto/jdk21/serialization/Recipes.java | 20 -- .../xef/java/auto/jdk21/tot/Checker.java | 10 - .../java/auto/jdk21/tot/ControlSignals.java | 36 ---- .../xef/java/auto/jdk21/tot/Critiques.java | 39 ---- .../xef/java/auto/jdk21/tot/Main.java | 39 ---- .../xef/java/auto/jdk21/tot/Problems.java | 116 ----------- .../xef/java/auto/jdk21/tot/Rendering.java | 38 ---- .../xef/java/auto/jdk21/tot/Solutions.java | 68 ------ .../xef/java/auto/jdk21/util/ConsoleUtil.java | 31 --- examples/scala/.scalafmt.conf | 36 ---- examples/scala/build.gradle.kts | 30 --- examples/scala/src/main/resources/logback.xml | 26 --- .../xef/examples/scala/Simple.scala | 10 - .../scala/context/serpapi/Simple.scala | 45 ---- .../scala/context/serpapi/UserQueries.scala | 20 -- .../examples/scala/images/HybridCity.scala | 10 - .../scala/iteration/AnimalStory.scala | 20 -- .../examples/scala/iteration/ChessGame.scala | 52 ----- .../scala/serialization/Annotated.scala | 18 -- .../examples/scala/serialization/Simple.scala | 37 ---- gradle.properties | 4 +- gradle/libs.versions.toml | 13 -- java/README.md | 26 --- java/build.gradle.kts | 32 --- .../xebia/functional/xef/java/auto/Empty.java | 7 - scala/.scalafmt.conf | 36 ---- scala/README.md | 29 --- scala/build.gradle.kts | 38 ---- scala/src/main/resources/logback.xml | 26 --- .../xef/scala/conversation/package.scala | 65 ------ .../serialization/KotlinXSerializers.scala | 18 -- .../serialization/SerialDescriptor.scala | 116 ----------- .../SerialDescriptorInstances.scala | 64 ------ .../serialization/SerialDescriptorSpec.scala | 68 ------ settings.gradle.kts | 21 -- 56 files changed, 11 insertions(+), 2257 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/ScalaPublishingConventionsPlugin.kt delete mode 100644 buildSrc/src/main/kotlin/xef-scala-publishing-conventions.gradle.kts delete mode 100644 docs/intro/java.md delete mode 100644 docs/intro/scala.md delete mode 100644 examples/java/build.gradle.kts delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/BreakingNews.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/DivergentTasks.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Markets.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/PDFDocument.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Weather.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/conversations/Animals.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/gpt4all/Chat.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ASCIIArt.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Book.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Books.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ChessAI.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Movies.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Recipes.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Checker.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/ControlSignals.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Critiques.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Main.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Problems.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Rendering.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Solutions.java delete mode 100644 examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/util/ConsoleUtil.java delete mode 100644 examples/scala/.scalafmt.conf delete mode 100644 examples/scala/build.gradle.kts delete mode 100644 examples/scala/src/main/resources/logback.xml delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/Simple.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/Simple.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/UserQueries.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/images/HybridCity.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/AnimalStory.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/ChessGame.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Annotated.scala delete mode 100644 examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Simple.scala delete mode 100644 java/README.md delete mode 100644 java/build.gradle.kts delete mode 100644 java/src/main/java/com/xebia/functional/xef/java/auto/Empty.java delete mode 100644 scala/.scalafmt.conf delete mode 100644 scala/README.md delete mode 100644 scala/build.gradle.kts delete mode 100644 scala/src/main/resources/logback.xml delete mode 100644 scala/src/main/scala/com/xebia/functional/xef/scala/conversation/package.scala delete mode 100644 scala/src/main/scala/com/xebia/functional/xef/scala/serialization/KotlinXSerializers.scala delete mode 100644 scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala delete mode 100644 scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala delete mode 100644 scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala diff --git a/.github/workflows/publish-development-version.yml b/.github/workflows/publish-development-version.yml index d373c3827..36fa1407a 100644 --- a/.github/workflows/publish-development-version.yml +++ b/.github/workflows/publish-development-version.yml @@ -34,7 +34,7 @@ jobs: - name: Assemble uses: gradle/gradle-build-action@v2 with: - arguments: assemble -x:xef-java-examples:assemble -x:xef-scala:assemble -x:xef-scala-examples:assemble + arguments: assemble - name: Upload reports if: failure() @@ -46,36 +46,4 @@ jobs: - name: Publish development version uses: gradle/gradle-build-action@v2 with: - arguments: publishToSonatype -x:xef-scala:publishToSonatype closeAndReleaseSonatypeStagingRepository - - publish-modules-with-loom: - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Set up Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 20 - - - name: assemble - uses: gradle/gradle-build-action@v2 - with: - arguments: :xef-scala:assemble :xef-java-examples:assemble - - - name: Upload reports - if: failure() - uses: actions/upload-artifact@v3 - with: - name: 'reports-${{ matrix.os }}' - path: '**/build/reports/**' - - - name: Publish development version - uses: gradle/gradle-build-action@v2 - with: - arguments: :xef-scala:publishToSonatype closeAndReleaseSonatypeStagingRepository + arguments: publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f6523d87c..10b5e15a4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: - name: Assemble uses: gradle/gradle-build-action@v2 with: - arguments: assemble -x:xef-java-examples:assemble -x:xef-scala:assemble -x:xef-scala-examples:assemble + arguments: assemble - name: Upload reports if: failure() @@ -44,36 +44,4 @@ jobs: - name: Publish final version uses: gradle/gradle-build-action@v2 with: - arguments: publishToSonatype -x:xef-scala:publishToSonatype closeSonatypeStagingRepository - - publish-modules-with-loom: - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Set up Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 20 - - - name: assemble - uses: gradle/gradle-build-action@v2 - with: - arguments: :xef-scala:assemble :xef-java-examples:assemble - - - name: Upload reports - if: failure() - uses: actions/upload-artifact@v3 - with: - name: 'reports-${{ matrix.os }}' - path: '**/build/reports/**' - - - name: Publish final version - uses: gradle/gradle-build-action@v2 - with: - arguments: :xef-scala:publishToSonatype closeSonatypeStagingRepository + arguments: publishToSonatype closeSonatypeStagingRepository diff --git a/README.md b/README.md index dccf15acd..b03c15fb2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Our goal is to make the move to this new world as simple as possible for the dev xef.ai is packaged in two layers: 1. Core libraries bringing integration with the basic services in an AI application. These libraries expose an _idiomatic_ interface, so there's one per programming language. - At this moment we support Kotlin and Scala. + At this moment we support Kotlin. 2. Integrations with other libraries which complement the core mission of xef.ai. xef.ai draws inspiration from libraries like [LangChain](https://docs.langchain.com/docs/) @@ -47,10 +47,9 @@ strategies. Libraries are published in Maven Central, under the `com.xebia` group. -1. `xef-kotlin` for Kotlin support, `xef-scala` for Scala, `xef-java` for Java. +1. `xef-kotlin` for Kotlin support. 2. The name of a library we provide integration for, like `xef-lucene`. -
Kotlin logo Gradle (Kotlin DSL) Libraries are published in Maven Central. You may need to add that repository explicitly @@ -73,51 +72,14 @@ We publish all libraries at once under the same version, so [version catalogs](https://docs.gradle.org/current/userguide/platforms.html#sec:sharing-catalogs) could be useful. -
- -
-Scala logo SBT - -```sbt -libraryDependencies += "com.xebia" %% "xef-scala" % "" -``` - -> **Warning** -> `xef-scala` is currently only available for Scala 3, and depends on project [Loom](https://openjdk.org/projects/loom/), -> so you will need at least Java 20 to use the library. - -
- -
-Maven logo Maven - -Libraries are published in Maven Central. You may need to add that repository explicitly -in your build, if you haven't done it before. - -```xml - - com.xebia - xef-java - x.x.x - pom - runtime - -``` - -
- ## 📖 Quick Introduction In this small introduction we look at the main features of xef, including the `conversation` function. -- [Kotlin logo Kotlin version](https://github.com/xebia-functional/xef/blob/main/docs/intro/kotlin.md) -- [Scala logo Scala version](https://github.com/xebia-functional/xef/blob/main/docs/intro/scala.md) -- [Java logo Java version](https://github.com/xebia-functional/xef/blob/main/docs/intro/java.md) +- [Kotlin logo Quick introduction to xef.ai](https://github.com/xebia-functional/xef/blob/main/docs/intro/kotlin.md) ## 🚀 Examples You can also have a look at the examples to have a feeling of how using the library looks like. - [Kotlin logo Examples in Kotlin](https://github.com/xebia-functional/xef/tree/main/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/conversation) -- [Scala logo Examples in Scala](https://github.com/xebia-functional/xef/tree/main/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala) -- [Java logo Examples in Java](https://github.com/xebia-functional/xef/tree/main/examples/java/src/main/java/com/xebia/functional/xef/java/auto) diff --git a/buildSrc/src/main/kotlin/ScalaPublishingConventionsPlugin.kt b/buildSrc/src/main/kotlin/ScalaPublishingConventionsPlugin.kt deleted file mode 100644 index 400817204..000000000 --- a/buildSrc/src/main/kotlin/ScalaPublishingConventionsPlugin.kt +++ /dev/null @@ -1,60 +0,0 @@ -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.plugins.BasePlugin -import org.gradle.api.plugins.BasePluginExtension -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.TaskProvider -import org.gradle.api.tasks.bundling.Jar -import org.gradle.kotlin.dsl.findByType -import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.register -import org.gradle.plugins.signing.SigningExtension - -class ScalaPublishingConventionsPlugin : Plugin { - - override fun apply(project: Project): Unit = project.run { - val scaladocJarTask: TaskProvider = tasks.register("scaladocJar") { - group = BasePlugin.BUILD_GROUP - tasks.findByName("scaladoc")?.let { dependsOn(it) } - ?: errorMessage("The scaladoc task was not found. The Javadoc jar file won't contain any documentation") - archiveClassifier.set("javadoc") - from("${layout.buildDirectory.get()}/docs/scaladoc") - } - - val publishingExtension: PublishingExtension = - extensions.findByType() - ?: throw IllegalStateException("The Maven Publish plugin is required to publish the build artifacts") - - val signingExtension: SigningExtension = - extensions.findByType() - ?: throw IllegalStateException("The Signing plugin is required to digitally sign the built artifacts") - - val basePluginExtension: BasePluginExtension = - extensions.findByType() - ?: throw IllegalStateException("The Base plugin is required to configure the name of artifacts") - - publishingExtension.run { - publications { - register("maven") { - val scala3Suffix = "_3" - artifactId = basePluginExtension.archivesName.get() + scala3Suffix - from(components["java"]) - artifact(scaladocJarTask) - pomConfiguration(project) - } - } - } - - signingExtension.run { - val isLocal = gradle.startParameter.taskNames.any { it.contains("publishToMavenLocal", ignoreCase = true) } - val signingKeyId: String? = configValue("signing.keyId", "SIGNING_KEY_ID") - val signingKey: String? = configValue("signing.key", "SIGNING_KEY") - val signingPassphrase: String? = configValue("signing.passphrase", "SIGNING_KEY_PASSPHRASE") - isRequired = !isLocal - useGpgCmd() - useInMemoryPgpKeys(signingKeyId, signingKey, signingPassphrase) - sign(publishingExtension.publications) - } - } -} diff --git a/buildSrc/src/main/kotlin/xef-scala-publishing-conventions.gradle.kts b/buildSrc/src/main/kotlin/xef-scala-publishing-conventions.gradle.kts deleted file mode 100644 index 97fa1cfd8..000000000 --- a/buildSrc/src/main/kotlin/xef-scala-publishing-conventions.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -apply() diff --git a/core/TECHNICAL.MD b/core/TECHNICAL.MD index 22b88ceb0..70969ee8e 100644 --- a/core/TECHNICAL.MD +++ b/core/TECHNICAL.MD @@ -14,11 +14,9 @@ The breakdown is only in terms of JVM, since that's where we _mostly_ care about We include the following dependencies in our _core_ module to implement a _common_ layer to interact with LLMs. The dependency on Kotlin Stdlib is unavoidable, since we use Kotlin as our main language. -We also require KotlinX Coroutines such that we can leverage the `suspend` keyword in our API and expose `Future` to the -Java/Scala API. -Additionally, we also have a need for a HTTP client, and a serialization framework. Here we use Ktor and KotlinX -Serialization respectively, -and Xef relies on the CIO engine for Ktor, which avoids any additional dependencies. +We also require KotlinX Coroutines such that we can leverage the `suspend` keyword in our API. +Additionally, we also have a need for an HTTP client, and a serialization framework. Here we use Ktor and KotlinX +Serialization respectively, and Xef relies on the CIO engine for Ktor, which avoids any additional dependencies. - kotlin-stdlib (1810 Kb = 1598 Kb + 212 Kb) - kotlinx-coroutines-core (1608 Kb = 1442 Kb + 166 Kb) diff --git a/docs/intro/java.md b/docs/intro/java.md deleted file mode 100644 index 2d16ccdc7..000000000 --- a/docs/intro/java.md +++ /dev/null @@ -1,193 +0,0 @@ -# Quick introduction to xef.ai (Java version) - -After adding the library to your project -(see the [main README](https://github.com/xebia-functional/xef/blob/main/README.md) for instructions), -you get access to the `AIScope` class, which is your port of entry to the modern AI world. -Using it, you can _prompt_ for information, which means posing the question to an LLM -(Large Language Model). The easiest way is to just get the information back as a string. - -```java -package my.example; - -import com.xebia.functional.xef.java.auto.AIScope; - -import java.util.concurrent.ExecutionException; - -public class Example { - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (AIScope scope = new AIScope()) { - String topic = "artificial intelligence"; - scope.promptMessage("Give me a selection of books about " + topic) - .thenAccept(System.out::println) - .get(); - } - } -} -``` - -> **Note** -> By default the `AIScope` connects to [OpenAI](https://platform.openai.com/). -> To use their services you should provide the corresponding API key in the `OPENAI_TOKEN` -> environment variable, and have enough credits. - -In the example above we create an `AIScope` using the `try-with-resources` syntax, -which ensures that the scope is closed at the end of the block. -The `AIScope` gives us access to the `promptMessage` & co functions, which allow us to interact with the LLM. - -All the functions of `AIScope` are returned as a `Future` for maximum backward compatibility until JDK8, -but you can inject `Executors.newVirtualThreadPerTaskExecutor()` to have the `Future`s work on virtual threads. - -Remember that exceptions in `Future`are wrapped in `ExecutionException`, -so to inspect the actual exception you need to call `getCause()` on it. -_Structured Concurrency_ is implemented under the hood by Kotlin's `CoroutineScope`, -and all futures are cancelled when the `AIScope` is closed and `Future#get` will throw `CancellationException`. - -In the next examples we'll write functions that rely on `AIScope`'s DSL functionality - -## Structure - - -The output from the `books` function above may be hard to parse back from the -strings we obtain. Fortunately, you can also ask xef.ai to give you back the information -using a _custom type_. The library takes care of instructing the LLM on building such -a structure, and deserialize the result back for you. - -We can thus define a `Book` class that describes the desired response we want to receive from the LLM. -Relying on [Jakarta validation](https://beanvalidation.org) we can also specify which fields are mandatory using `NotNull`, -or include additional constraints in the [Json Schema](https://json-schema.org). - -xef.ai reuses [Jackson](https://github.com/FasterXML/jackson-databind), -and [JsonSchema generator](https://github.com/victools/jsonschema-generator) to parse and generate the Json Schema V7 for you. - -```java -package my.example; - -import jakarta.validation.constraints.NotNull; - -public class Book { - @NotNull public String title; - @NotNull public String author; - @NotNull public int year; - @NotNull public String genre; - - @Override - public String toString() { - return "Book{" + - "title='" + title + '\'' + - ", author='" + author + '\'' + - ", year=" + year + - ", genre='" + genre + '\'' + - '}'; - } -} -``` - -Using the definition of `Book`, we can rewrite our previous example as: - -```java -package my.example; - -import com.xebia.functional.xef.java.auto.AIScope; -import jakarta.validation.constraints.NotNull; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class Example { - - private final AIScope scope; - - public Example(AIScope scope) { - this.scope = scope; - } - - public CompletableFuture bookSelection(String topic) { - return scope.prompt("Give me a selection of books about " + topic, Example.Book.class); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (AIScope scope = new AIScope()) { - Example example = new Example(scope); - example.bookSelection("artificial intelligence") - .thenAccept(System.out::println) - .get(); - } - } -} -``` - -Here we also show how you can easily capture the `AIScope` in a class, -and build and compose additional functionality on top. -If you're using any dependency injection framework, you can also construct `AIScope` and inject it as usual. -Make sure that the dependency injection framework properly closes the `AIScope` when the application shuts down. - -## Context - -LLMs have knowledge about a broad variety of topics. But by construction they are not able -to respond to questions about information not available in their training set. However, you -often want to supplement the LLM with more data: -- Transient information referring to the current moment, like the current weather, or - the trends in the stock market in the past 10 days. -- Non-public information, for example for summarizing a piece of text you're creating - within you organization. - -These additional pieces of information are called the _contextScope_ in xef.ai, and are attached -to every question to the LLM. Although you can add arbitrary strings to the context at any -point, the most common mode of usage is using an _agent_ to consult an external service, -and make its response part of the context. One such agent is `search`, which uses a web -search service to enrich that context. - -```java -package my.example; - -import java.util.concurrent.CompletableFuture; - -public class Weather { - private final AIScope scope; - - public Weather(AIScope scope) { - this.scope = scope; - } - - public CompletableFuture recommendation() { - return scope.contextScope(scope.search("Weather in $place"), () -> - scope.promptMessage("Knowing this forecast, what clothes do you recommend I should wear?") - ); - } -} -``` - -> **Note** -> The underlying mechanism of the context is a _vector store_, a data structure which -> saves a set of strings, and is able to find those similar to another given one. -> By default xef.ai uses an _in-memory_ vector store, since it provides maximum -> compatibility across platforms. However, if you foresee your context growing above -> the hundreds of elements, you may consider switching to another alternative, like -> Lucene or PostgreSQL. -> -> ```java -> package my.example; -> -> import com.xebia.functional.xef.store.LuceneKt; -> -> import java.nio.file.Path; -> import java.util.concurrent.CompletableFuture; -> -> public class VectorStore { -> -> private final AIScope scope; -> -> public VectorStore(AIScope scope) { -> this.scope = scope; -> } -> -> public void example() { -> Path LUCENE_PATH = Path.of("lucene"); -> scope.contextScope( -> LuceneKt.InMemoryLuceneBuilder(LUCENE_PATH), -> () -> CompletableFuture.completedFuture("do stuff") -> ); -> } -> -> } -> ``` diff --git a/docs/intro/scala.md b/docs/intro/scala.md deleted file mode 100644 index 3ecb49a23..000000000 --- a/docs/intro/scala.md +++ /dev/null @@ -1,146 +0,0 @@ -# Quick introduction to xef.ai (Scala version) - -After adding the library to your project (see the -[main README](https://github.com/xebia-functional/xef/blob/main/README.md) for instructions), -you get access to the `conversation` function, which is your port of entry to the modern AI world. -Inside of it, you can _prompt_ for information, which means posing the question to an LLM -(Large Language Model). The easiest way is to just get the information back as a string or list of strings. - -```scala 3 -import com.xebia.functional.xef.scala.conversation.* - -def books(topic: String): Unit = conversation: - val topBook: String = promptMessage(s"Give me the top-selling book about $topic") - println(topBook) - val selectedBooks: List[String] = promptMessages(s"Give me a selection of books about $topic") - println(selectedBooks.mkString("\n")) -``` - -## Additional setup - -If the code above fails, you may need to perform some additional setup. - -### OpenAI - -By default, the `conversation` block connects to [OpenAI](https://platform.openai.com/). -To use their services you should provide the corresponding API key in the `OPENAI_TOKEN` -environment variable, and have enough credits. - -
-SBT - -```shell -env OPENAI_TOKEN= sbt -``` -
- -
-IntelliJ - -Set the environment variable `OPENAI_TOKEN=xxx` - -
- -### Project Loom - -The Scala module depends on project [Loom](https://openjdk.org/projects/loom/), -so you will need at least Java 20 to use the library. Furthermore, if using Java 20 specifically, -you need to pass the `--enable-preview` flag. - -
-SBT - -```shell -env OPENAI_TOKEN= sbt -J--enable-preview -``` -
- -
-IntelliJ - -- Set the Java version to at least 20 -- If using Java 20 specifically, set VM options to `--enable-preview` - -
- -## Structure - -The output from the `books` function above may be hard to parse back from the -strings we obtain. Fortunately, you can also ask xef.ai to give you back the information -using a _custom type_. The library takes care of instructing the LLM on building such -a structure, and deserialize the result back for you. - -This can be done by declaring a case class that `derives SerialDescriptor, Decoder`: - -```scala 3 -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -case class Book(name: String, author: String, pages: Int) derives SerialDescriptor, Decoder -``` - -The `conversation` block can then be written in this way: - -```scala 3 -def bookExample(topic: String): Unit = conversation: - val Book(title, author, pages) = prompt[Book](s"Give me the best-selling book about $topic") - println(s"The book $title is by $author and has $pages pages.") -``` - -xef.ai for Scala uses xef.ai core, which is based on the Kotlin implementation. Hence, the core -reuses [Kotlin's common serialization](https://kotlinlang.org/docs/serialization.html), and -Scala uses [circe](https://github.com/circe/circe) to derive the required serializable instance. -The LLM is usually able to detect which kind of information should go in each field based on its name -(like `title` and `author` above). - -## Context - -LLMs have knowledge about a broad variety of topics. But by construction they are not able -to respond to questions about information not available in their training set. However, you -often want to supplement the LLM with more data: -- Transient information referring to the current moment, like the current weather, or - the trends in the stock market in the past 10 days. -- Non-public information, for example for summarizing a piece of text you're creating - within your organization. - -These additional pieces of information are called the _context_ in xef.ai, and are attached -to every question to the LLM. Although you can add arbitrary strings to the context at any -point, the most common mode of usage is using an _agent_ to consult an external service, -and make its response part of the context. One such agent is `search`, which uses the -[Google Search API (SerpApi)](https://serpapi.com/) to enrich that context. - -(Note that a SerpApi token may be required to run this example.) - -```scala 3 -import com.xebia.functional.xef.conversation.llm.openai.* -import com.xebia.functional.xef.reasoning.serpapi.* -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -val openAI: OpenAI = OpenAI.FromEnvironment - -def setContext(query: String)(using conversation: ScalaConversation): Unit = - addContext(Search(openAI.DEFAULT_CHAT, conversation, 3).search(query).get) - -@main def runWeather(): Unit = conversation: - setContext("Weather in Cádiz, Spain") - val question = "Knowing this forecast, what clothes do you recommend I should wear if I live in Cádiz?" - val answer = promptMessage(question) - println(answer) -``` - -> **Note** -> The underlying mechanism of the context is a _vector store_, a data structure which -> saves a set of strings, and is able to find those similar to another given one. -> By default xef.ai uses an _in-memory_ vector store, since this provides maximum -> compatibility across platforms. However, if you foresee your context growing above -> the hundreds of elements, you may consider switching to another alternative, like -> Lucene or PostgreSQL. - -## Examples - -Check out the -[examples folder](https://github.com/xebia-functional/xef/blob/main/examples/scala/src/main/scala/com/xebia/functional/xef/examples) -for a complete list of different use cases. diff --git a/examples/java/build.gradle.kts b/examples/java/build.gradle.kts deleted file mode 100644 index aded6cd45..000000000 --- a/examples/java/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - java - alias(libs.plugins.spotless) -} - -dependencies { - implementation(projects.xefJava) - implementation(projects.xefReasoning) - implementation(projects.xefGpt4all) -} - -tasks.test { - useJUnitPlatform() -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/BreakingNews.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/BreakingNews.java deleted file mode 100644 index 71585a02c..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/BreakingNews.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.contexts; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import com.xebia.functional.xef.reasoning.serpapi.Search; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class BreakingNews { - - static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/M/yyyy"); - static LocalDateTime now = LocalDateTime.now(); - - public record BreakingNew(String summary) { - } - - private static CompletableFuture writeParagraph(PlatformConversation scope) { - var currentDate = dtf.format(now); - - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("write a paragraph of about 300 words about: " + currentDate + " Covid News"), BreakingNews.BreakingNew.class) - .thenAccept(breakingNews -> System.out.println(currentDate + " Covid news summary:\n" + breakingNews)); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - var currentDate = dtf.format(now); - var search = new Search(OpenAI.FromEnvironment.DEFAULT_CHAT, scope, 3); - scope.addContextFromArray(search.search(currentDate + " Covid News").get()); - writeParagraph(scope).get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/DivergentTasks.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/DivergentTasks.java deleted file mode 100644 index 6ed37bfac..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/DivergentTasks.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.contexts; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import com.xebia.functional.xef.reasoning.serpapi.Search; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class DivergentTasks { - - public Long numberOfMedicalNeedlesInWorld; - - private static CompletableFuture numberOfMedical(PlatformConversation scope) { - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("Provide the number of medical needles in the world"), DivergentTasks.class) - .thenAccept(numberOfNeedles -> System.out.println("Needles in world:\n" + numberOfNeedles.numberOfMedicalNeedlesInWorld)); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - Search search = new Search(OpenAI.FromEnvironment.DEFAULT_CHAT, scope, 3); - scope.addContextFromArray(search.search("Estimate amount of medical needles in the world").get()); - numberOfMedical(scope).get(); - } - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Markets.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Markets.java deleted file mode 100644 index 4cef77efa..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Markets.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.contexts; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import com.xebia.functional.xef.reasoning.serpapi.Search; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class Markets { - - public record Market(String news, List raisingStockSymbols, List decreasingStockSymbols) { - } - - private static CompletableFuture stockMarketSummary(PlatformConversation scope) { - var news = new Prompt("Write a short summary of the stock market results given the provided context."); - - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, news, Market.class) - .thenAccept(markets -> System.out.println(markets)); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - var dtf = DateTimeFormatter.ofPattern("dd/M/yyyy"); - var now = LocalDateTime.now(); - var currentDate = dtf.format(now); - var search = new Search(OpenAI.FromEnvironment.DEFAULT_CHAT, scope, 3); - scope.addContextFromArray(search.search(currentDate + "Stock market results, raising stocks, decreasing stocks").get()); - stockMarketSummary(scope).get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/PDFDocument.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/PDFDocument.java deleted file mode 100644 index e57b69aa6..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/PDFDocument.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.contexts; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.java.auto.jdk21.util.ConsoleUtil; -import com.xebia.functional.xef.prompt.Prompt; -import com.xebia.functional.xef.reasoning.pdf.PDF; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public class PDFDocument { - - private static ConsoleUtil util = new ConsoleUtil(); - - public record AIResponse(String answer, String source){} - - private static final String PDF_URL = "https://people.cs.ksu.edu/~schmidt/705a/Scala/Programming-in-Scala.pdf"; - - private static CompletableFuture askQuestion(PlatformConversation scope) { - System.out.println("Enter your question ( to exit): "); - - - var line = util.readLine(); - if (line == null || line.isBlank()) { - return CompletableFuture.completedFuture(null); - } else { - scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt(line), AIResponse.class) - .thenAccept(aiRes -> System.out.println(aiRes.answer + "\n---\n" + - aiRes.source + "\n---\n")); - - return askQuestion(scope); - } - } - - public static void main(String[] args) throws Exception { - try (PlatformConversation scope = OpenAI.conversation()) { - PDF pdf = new PDF(OpenAI.FromEnvironment.DEFAULT_CHAT, - OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, scope); - scope.addContext(List.of(pdf.readPDFFromUrl.readPDFFromUrl(PDF_URL).get())); - askQuestion(scope).get(); - } - finally { - util.close(); - } - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Weather.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Weather.java deleted file mode 100644 index 17b66c96a..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/contexts/Weather.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.contexts; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import com.xebia.functional.xef.reasoning.serpapi.Search; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class Weather { - public List answer; - - private static CompletableFuture clothesRecommend(PlatformConversation scope) { - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("Knowing this forecast, what clothes do you recommend I should wear?"), Weather.class) - .thenAccept(weather -> - System.out.println(weather.answer) - ); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - Search search = new Search(OpenAI.FromEnvironment.DEFAULT_CHAT, scope, 3); - scope.addContextFromArray(search.search("Weather in Cádiz, Spain").get()); - clothesRecommend(scope).get(); - } - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/conversations/Animals.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/conversations/Animals.java deleted file mode 100644 index 9ff1524c3..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/conversations/Animals.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.conversations; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.JvmPromptBuilder; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class Animals { - - private final PlatformConversation scope; - - public Animals(PlatformConversation scope) { - this.scope = scope; - } - - public CompletableFuture uniqueAnimal() { - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("A unique animal species."), Animal.class); - } - - public CompletableFuture groundbreakingInvention() { - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("A groundbreaking invention from the 20th century."), Invention.class); - } - - public CompletableFuture story(Animal animal, Invention invention) { - Prompt storyPrompt = new JvmPromptBuilder() - .addSystemMessage("You are a writer for a science fiction magazine.") - .addUserMessage("Write a short story of 200 words that involves the animal and the invention") - .build(); - return scope.promptMessage(OpenAI.FromEnvironment.DEFAULT_CHAT, storyPrompt); - } - - public record Animal(String name, String habitat, String diet){} - public record Invention(String name, String inventor, int year, String purpose){} - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - Animals animals = new Animals(scope); - animals.uniqueAnimal() - .thenCompose(animal -> - animals.groundbreakingInvention() - .thenCompose(invention -> - animals.story(animal, invention) - .thenAccept(System.out::println) - )).get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/gpt4all/Chat.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/gpt4all/Chat.java deleted file mode 100644 index 6c16baded..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/gpt4all/Chat.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.gpt4all; - -import com.xebia.functional.gpt4all.GPT4All; -import com.xebia.functional.gpt4all.Gpt4AllModel; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Path; -import java.util.Objects; -import java.util.concurrent.ExecutionException; - -public class Chat { - public static void main(String[] args) throws ExecutionException, InterruptedException, IOException { - var userDir = System.getProperty("user.dir"); - var path = userDir + "/models/gpt4all/ggml-replit-code-v1-3b.bin"; - - var supportedModels = Gpt4AllModel.Companion.getSupportedModels(); - - supportedModels.forEach(it -> { - var url = (Objects.nonNull(it.getUrl())) ? " - " + it.getUrl() : ""; - System.out.println("🤖 " + it.getName() + url); - }); - - var url = "https://huggingface.co/nomic-ai/ggml-replit-code-v1-3b/resolve/main/ggml-replit-code-v1-3b.bin"; - var modelPath = Path.of(path); - var gpt4all = GPT4All.Companion.invoke(url, modelPath); - - System.out.println("🤖 GPT4All loaded: " + gpt4all); - /** - * Uses internally [HuggingFaceLocalEmbeddings] default of "sentence-transformers", "msmarco-distilbert-dot-v5" - * to provide embeddings for docs in contextScope. - */ - - try (var scope = OpenAI.conversation(); - var br = new BufferedReader(new InputStreamReader(System.in))) { - - System.out.println("\n🤖 Enter your question: "); - - while(true){ - String line = br.readLine(); - if (line.equals("exit")) break; - - var answer = scope.promptStreamingToPublisher(gpt4all, new Prompt(line)); - - answer.subscribe(new Subscriber() { - StringBuilder answer = new StringBuilder(); - - @Override - public void onSubscribe(Subscription s) { - System.out.print("\n🤖 --> " + s); - s.request(Long.MAX_VALUE); - } - - @Override - public void onNext(String s) { - answer.append(s); - } - - @Override - public void onError(Throwable t) { - System.out.println(t); - } - - @Override - public void onComplete() { - System.out.println("\n🤖 --> " + answer.toString()); - System.out.println("\n🤖 --> Done"); - System.out.println("\n🤖 Enter your question: "); - } - }); - } - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ASCIIArt.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ASCIIArt.java deleted file mode 100644 index e04fd177e..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ASCIIArt.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.concurrent.ExecutionException; - -public class ASCIIArt { - public String art; - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("ASCII art of a cat dancing"), ASCIIArt.class) - .thenAccept(art -> System.out.println(art.art)) - .get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Book.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Book.java deleted file mode 100644 index 3f090bec9..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Book.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.concurrent.ExecutionException; - -public class Book { - - public String title; - public String author; - public String summary; - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("To Kill a Mockingbird by Harper Lee summary."), Book.class) - .thenAccept(book -> System.out.println("To Kill a Mockingbird summary:\n" + book.summary)) - .get(); - } - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Books.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Books.java deleted file mode 100644 index 75f6487da..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Books.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; -import jakarta.validation.constraints.NotNull; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class Books { - - private final PlatformConversation scope; - - public Books(PlatformConversation scope) { - this.scope = scope; - } - - public record Book(@NotNull String title, @NotNull String author, @NotNull int year, @NotNull String genre){} - - public CompletableFuture bookSelection(String topic) { - return scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("Give me a selection of books about " + topic), Books.Book.class); - } - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - Books books = new Books(scope); - books.bookSelection("artificial intelligence") - .thenAccept(System.out::println) - .get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ChessAI.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ChessAI.java deleted file mode 100644 index bb50947d5..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/ChessAI.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.ArrayList; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -public class ChessAI { - - public record ChessMove(String player, String move){} - public record ChessBoard(String board){} - public record GameState(Boolean ended, String winner){} - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - var moves = new ArrayList(); - var gameEnded = false; - var winner = ""; - - while (!gameEnded) { - var currentPlayer = ((moves.size() % 2) == 0) ? "Player 1 (White)" : "Player 2 (Black)"; - - var prompt = String.format(""" - |%s, it's your turn. - |Previous moves: %s - |Make your next move:""", - currentPlayer, - moves.stream().map(ChessMove::toString).collect(Collectors.joining(", "))); - - ChessMove move = scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt(prompt), ChessMove.class).get(); - moves.add(move); - - // Update boardState according to move.move - // ... - - var boardPrompt = String.format(""" - Given the following chess moves: %s, - generate a chess board on a table with appropriate emoji representations for each move and piece. - Add a brief description of the move and it's implications""", - moves.stream().map(it -> it.player + ":" + it.move).collect(Collectors.joining(", "))); - - ChessBoard chessBoard= scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt(boardPrompt), ChessBoard.class).get(); - System.out.println("Current board:\n" + chessBoard.board); - - var gameStatePrompt = String.format(""" - Given the following chess moves: %s, - has the game ended (win, draw, or stalemate)?""", - moves.stream().map(ChessMove::toString).collect(Collectors.joining(", "))); - - GameState gameState = scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt(gameStatePrompt), GameState.class).get(); - - gameEnded = gameState.ended; - winner = gameState.winner; - } - - System.out.println("Game over. Final move: " + moves.get(moves.size() - 1) + ", Winner: " + winner); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Movies.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Movies.java deleted file mode 100644 index 2ae736fd8..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Movies.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.concurrent.ExecutionException; - -public class Movies { - - public record Movie(String title, String genre, String director){} - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("Please provide a movie title, genre and director for the Inception movie"), Movie.class) - .thenAccept(movie -> System.out.println(movie)) - .get(); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Recipes.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Recipes.java deleted file mode 100644 index 1439c72af..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/serialization/Recipes.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.serialization; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import java.util.List; -import java.util.concurrent.ExecutionException; - -public class Recipes { - - public record Recipe(String name, List ingredients){} - - public static void main(String[] args) throws ExecutionException, InterruptedException { - try (PlatformConversation scope = OpenAI.conversation()) { - var recipe = scope.prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, new Prompt("Recipe for chocolate chip cookies."), Recipe.class).get(); - System.out.println("The recipe for " + recipe.name + " is " + recipe.ingredients ); - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Checker.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Checker.java deleted file mode 100644 index 59a53b3a8..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Checker.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -public class Checker { - - public static Solutions.Solution checkSolution(Solutions.Solution response){ - System.out.println("✅ Validating solution: " + Rendering.truncateText(response.answer) + "..."); - return response.isValid ? response : - Solutions.makeSolution(response.answer, false, "Invalid solution", null); - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/ControlSignals.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/ControlSignals.java deleted file mode 100644 index 82584872c..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/ControlSignals.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.renderHistory; -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.truncateText; - -import java.util.concurrent.CompletableFuture; - -public class ControlSignals { - - static class ControlSignal { - public String value = ""; - } - - public static CompletableFuture controlSignal(Problems.Memory memory){ - System.out.println("\uD83E\uDDE0 Generating control signal for problem:" + truncateText(memory.problem.description) + "..."); - var guidancePrompt = new Prompt(Rendering.trimMargin( - " You are an expert advisor on information extraction.\n" + - " You generate guidance for a problem.\n" + - " " + renderHistory(memory) + "\n" + - " You are given the following problem:\n" + - " " + memory.problem.description + "\n" + - " Instructions:\n" + - " 1. Generate 1 guidance to get the best results for this problem.\n" + - " 2. Ensure the guidance is relevant to the problem.\n" + - " 3. Ensure the guidance is accurate, complete, and unambiguous.\n" + - " 4. Ensure the guidance is actionable.\n" + - " 5. Ensure the guidance accounts for previous answers in the `history`.\n" + - " \n")); - - return Problems.Memory.getAiScope().prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, guidancePrompt, ControlSignal.class); - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Critiques.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Critiques.java deleted file mode 100644 index b835c0c69..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Critiques.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.truncateText; - -import java.util.concurrent.CompletableFuture; - -public class Critiques { - - static class Critique { - public String answer; - public String reasoning; - public boolean answerTrulyAccomplishesTheGoal; - } - - public static CompletableFuture critique(Problems.Memory memory, Solutions.Solution currentSolution){ - System.out.println("🕵️ Critiquing solution: " + truncateText(currentSolution.answer) + "..."); - - var prompt = new Prompt(Rendering.trimMargin( - " You are an expert advisor critiquing a solution.\n" + - " \n" + - " Previous history:\n" + - " " + Rendering.renderHistory(memory) + "\n" + - " \n" + - " You are given the following problem:\n" + - " " + memory.problem.description + "\n" + - " \n" + - " You are given the following solution:\n" + - " " + currentSolution.answer + "\n" + - " \n" + - " Instructions:\n" + - " 1. Provide a critique and determine if the answer truly accomplishes the goal.\n" + - " \n")); - - return Problems.Memory.getAiScope().prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, prompt, Critique.class); - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Main.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Main.java deleted file mode 100644 index 67cae5025..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Main.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -public class Main { - - private static final int MAX_ROUNDS = 5; - - public static void main(String[] args) { - Problems.Problem problem = new Problems.Problem(); - problem.description = Rendering.trimMargin( - " You are an expert functional programmer.\n" + - " 1. You never throw exceptions.\n" + - " 2. You never use null.\n" + - " 3. You never use `for` `while` or loops in general, prefer tail recursion.\n" + - " 4. You never use mutable state.\n" + - " \n" + - " This code is unsafe. Find the problems in this code and provide a Github suggestion code fence with the `diff` to fix it.\n" + - " \n" + - " ```kotlin\n" + - " fun access(list: List, index: Int): Int {\n" + - " return list[index]\n" + - " }\n" + - " ```\n" + - " \n" + - " Return a concise solution that fixes the problems in the code."); - - - var solve = Problems.solve(problem, MAX_ROUNDS); - - System.out.println("✅ Final solution: " + solve.answer); - System.out.println("✅ Solution validity: " + solve.isValid); - System.out.println("✅ Solution reasoning: " + solve.reasoning); - System.out.println("✅ Solution code: " + solve.value); - } - - static class FinalSolution { - public String solution; - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Problems.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Problems.java deleted file mode 100644 index 167225f00..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Problems.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -import com.xebia.functional.xef.conversation.PlatformConversation; -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.truncateText; - -public class Problems { - - static class Problem{ - public String description; - } - - public static Solutions.Solution solve(Problem problem, int maxRounds) { - try(Memory initialMemory = new Memory<>(problem, new ArrayList<>())) { - return solveRec(maxRounds, initialMemory); - } - } - - private static Solutions.Solution solveRec(int remainingRounds, Memory sMemory) { - if(remainingRounds <= 0){ - System.out.println("❌ Maximum rounds reached. Unable to find a solution."); - return Solutions.makeSolution("", false, "No Response", null); - } else{ - System.out.println("🌱 Solving problem: " + - truncateText(sMemory.problem.description, 100) + - " (Remaining rounds: " + remainingRounds + "..."); - - ControlSignals.ControlSignal controlSignal = getControlSignal(sMemory); - Solutions.Solution response = Solutions.solution(sMemory, controlSignal); - Solutions.Solution result = Checker.checkSolution(response); - Memory updatedMemory = sMemory.addResult(result); - if(result.isValid){ - System.out.println("✅ Solution found: " + truncateText(result.answer) + "!"); - Critiques.Critique critique = getCritique(result, updatedMemory); - if(critique != null && critique.answerTrulyAccomplishesTheGoal){ - System.out.println("❌ Solution does not accomplish the goal: " + truncateText(result.answer) + "!"); - System.out.println("⏪ Backtracking..."); - return solveRec(remainingRounds - 1, updatedMemory); - } - else { - return result; - } - } - else{ - System.out.println("⏪ Backtracking..."); - return solveRec(remainingRounds - 1, updatedMemory); - } - } - } - - private static ControlSignals.ControlSignal getControlSignal(Memory sMemory) { - try { - ControlSignals.ControlSignal controlSignal = ControlSignals.controlSignal(sMemory).get(); - System.out.println("\uD83E\uDDE0 Generated control signal: " + truncateText(controlSignal.value)); - return controlSignal; - } catch (Exception e) { - System.err.printf("ControlSignals.controlSignal prompt threw exception: %s - %s\n", - e.getClass().getName(), e.getMessage()); - return new ControlSignals.ControlSignal(); - } - } - - @Nullable - private static Critiques.Critique getCritique(Solutions.Solution result, Memory updatedMemory) { - try { - return Critiques.critique(updatedMemory, result).get(); - } catch (Exception e) { - System.err.printf("Critiques.critique prompt threw exception: %s - %s\n", - e.getClass().getName(), e.getMessage()); - return null; - } - } - - static class Memory implements AutoCloseable { - - public Problem problem; - public List> history; - - private static PlatformConversation aiScope = null; - - public Memory(Problem problem, List> history) { - this.problem = problem; - this.history = history; - checkPlatformConversation(); - } - - public Memory addResult(Solutions.Solution result) { - List> historyUpdate = Stream.concat(this.history.stream(), Stream.of(result)).toList(); - checkPlatformConversation(); - return new Memory<>(this.problem, historyUpdate); - } - - private static void checkPlatformConversation() { - if(aiScope == null){ - aiScope = OpenAI.conversation(); - } - } - - public static PlatformConversation getAiScope() { - return aiScope; - } - - public void close(){ - if(aiScope != null) { - aiScope.close(); - aiScope = null; - } - } - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Rendering.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Rendering.java deleted file mode 100644 index b1fdda157..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Rendering.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -import java.util.Arrays; -import java.util.stream.Collectors; - -public class Rendering { - - public static String trimMargin(String input){ - return Arrays.stream(input.split("\n")).map(String::trim).collect(Collectors.joining("\n")); - } - - public static String truncateText(String answer) { - return truncateText(answer, 150); - } - - public static String truncateText(String answer, int limit) { - if(answer == null) - return ""; - if(answer.length() > limit) { - answer = answer.substring(0, limit - 3) + "..."; - } - return answer.replace("\n", " "); - } - - public static String renderHistory(Problems.Memory memory){ - return trimMargin(" ```history \n\n" + - memory.history.stream() - .map(Rendering::renderHistoryItem) - .collect(Collectors.joining("\n")) + - "```"); - } - - private static String renderHistoryItem(Solutions.Solution solution){ - return trimMargin(solution.answer + "\n" + - solution.reasoning + "\n" + - (solution.isValid ? "✅" : "❌") + "\n"); - } -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Solutions.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Solutions.java deleted file mode 100644 index cdbe3cba9..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/tot/Solutions.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.tot; - -import com.xebia.functional.xef.conversation.llm.openai.OpenAI; -import com.xebia.functional.xef.prompt.Prompt; - -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.renderHistory; -import static com.xebia.functional.xef.java.auto.jdk21.tot.Rendering.truncateText; - -public class Solutions{ - - static class Solution{ - public String answer; - public boolean isValid; - public String reasoning; - - public A value; - } - - public static Solution makeSolution(String answer, boolean isValid, String reasoning, B value) { - Solution solution = new Solution<>(); - solution.isValid = isValid; - solution.answer = answer; - solution.reasoning = reasoning; - solution.value = value; - return solution; - } - - public static Solution solution(Problems.Memory memory, - ControlSignals.ControlSignal controlSignal){ - //ai emoji - System.out.println("\uD83E\uDD16 Generating solution for problem: " + truncateText(memory.problem.description) + "..."); - - Prompt enhancedPrompt = new Prompt( - " Given previous history:\n" + - " " + renderHistory(memory) + "\n" + - " Given the goal: \n" + - " ```goal\n" + - " " + memory.problem.description + " \n" + - " ```\n" + - " and considering the guidance: \n" + - " ```guidance\n" + - " " + controlSignal.value + "\n" + - " ```\n" + - " \n" + - " Instructions:\n" + - " \n" + - " 1. Please provide a comprehensive solution. \n" + - " 2. Consider all possible scenarios and edge cases. \n" + - " 3. Ensure your solution is accurate, complete, and unambiguous. \n" + - " 4. If you are unable to provide a solution, please provide a reason why and set `isValid` to false.\n" + - " 5. Include citations, references and links at the end to support your solution based on your sources.\n" + - " 6. Do not provide recommendations, only provide a solution.\n" + - " 7. when `isValid` is true Include in the `value` field the value of the solution according to the `value` json schema.\n" + - " 8. If no solution is found set the `value` field to `null`.\n" + - " 9. If the solution is not valid set the `isValid` field to `false` and the `value` field to `null`.\n" + - " 10. If the solution is valid set the `isValid` field to `true` and the `value` field to the value of the solution.\n" + - " \n"); - - try { - return Problems.Memory.getAiScope().prompt(OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, enhancedPrompt, Solution.class).get(); - } catch (Exception e) { - System.err.printf("Solutions.solution enhancedPrompt threw exception: %s - %s\n", - e.getClass().getName(), e.getMessage()); - return null; - } - } - -} diff --git a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/util/ConsoleUtil.java b/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/util/ConsoleUtil.java deleted file mode 100644 index a06678b28..000000000 --- a/examples/java/src/main/java/com/xebia/functional/xef/java/auto/jdk21/util/ConsoleUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xebia.functional.xef.java.auto.jdk21.util; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -public class ConsoleUtil implements AutoCloseable{ - - private final BufferedReader sysin; - - public ConsoleUtil(){ - sysin = new BufferedReader(new InputStreamReader(System.in)); - } - - /** - * Read line from the console (IDE friendly) - * @return line from console input - */ - public String readLine() { - try { - return sysin.readLine(); - } catch (IOException e) { - return null; - } - } - - @Override - public void close() throws Exception { - sysin.close(); - } -} diff --git a/examples/scala/.scalafmt.conf b/examples/scala/.scalafmt.conf deleted file mode 100644 index 038d0ede0..000000000 --- a/examples/scala/.scalafmt.conf +++ /dev/null @@ -1,36 +0,0 @@ -# tune this file as appropriate to your style! see: https://olafurpg.github.io/scalafmt/#Configuration - -version = "3.7.15" - -runner.dialect = "scala3" - -maxColumn = 150 - -continuationIndent.callSite = 2 - -newlines { - sometimesBeforeColonInMethodReturnType = false -} - -align { - arrowEnumeratorGenerator = false - ifWhileOpenParen = false - openParenCallSite = false - openParenDefnSite = false - tokens = ["%", "%%"] -} - -docstrings.style = Asterisk - -rewrite { - rules = [SortImports, RedundantBraces] - redundantBraces.maxLines = 1 -} - -optIn { - breaksInsideChains = true -} - -project.excludeFilters = [ - "metals.sbt" -] diff --git a/examples/scala/build.gradle.kts b/examples/scala/build.gradle.kts deleted file mode 100644 index 615adf2a5..000000000 --- a/examples/scala/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - scala - alias(libs.plugins.spotless) -} - -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - toolchain { languageVersion = JavaLanguageVersion.of(21) } -} - -dependencies { - implementation(projects.xefCore) - implementation(projects.xefScala) - implementation(projects.xefReasoning) - implementation(projects.xefOpenai) - implementation(libs.circe.parser) - implementation(libs.scala.lang) - implementation(libs.logback) -} - -tasks.withType().configureEach { useJUnit() } - -tasks.withType { - scalaCompileOptions.additionalParameters = listOf("-Wunused:all", "-Wvalue-discard") -} - -spotless { scala { scalafmt("3.7.15").configFile(".scalafmt.conf") } } diff --git a/examples/scala/src/main/resources/logback.xml b/examples/scala/src/main/resources/logback.xml deleted file mode 100644 index 8b62c8b02..000000000 --- a/examples/scala/src/main/resources/logback.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n - - - - - - - - - - - - - - - diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/Simple.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/Simple.scala deleted file mode 100644 index c9f4b17d1..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/Simple.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.xebia.functional.xef.examples.scala - -import com.xebia.functional.xef.scala.conversation.* - -@main def runBooks(): Unit = conversation: - val topic = "functional programming" - val topBook: String = promptMessage(s"Give me the top-selling book about $topic") - println(topBook) - val selectedBooks: List[String] = promptMessages(s"Give me a selection of books about $topic") - println(selectedBooks.mkString("\n")) diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/Simple.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/Simple.scala deleted file mode 100644 index fbe40da7e..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/Simple.scala +++ /dev/null @@ -1,45 +0,0 @@ -package com.xebia.functional.xef.examples.scala.context.serpapi - -import com.xebia.functional.xef.conversation.llm.openai.* -import com.xebia.functional.xef.reasoning.serpapi.* -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -import java.text.SimpleDateFormat -import java.util.Date - -val openAI: OpenAI = OpenAI.FromEnvironment - -val sdf = SimpleDateFormat("dd/M/yyyy") -def currentDate: String = sdf.format(new Date) - -def setContext(query: String)(using conversation: ScalaConversation): Unit = - addContext(Search(openAI.DEFAULT_CHAT, conversation, 3).search(query).get) - -case class BreakingNews(summary: String) derives SerialDescriptor, Decoder - -case class MarketNews(news: String, risingStockSymbols: List[String], fallingStockSymbols: List[String]) derives SerialDescriptor, Decoder - -case class Estimate(number: Long) derives SerialDescriptor, Decoder - -@main def runWeather(): Unit = conversation: - setContext("Weather in Cádiz, Spain") - val question = "Knowing this forecast, what clothes do you recommend I should wear if I live in Cádiz?" - val answer = promptMessage(question) - println(answer) - -@main def runBreakingNews(): Unit = conversation: - setContext(s"$currentDate COVID News") - val BreakingNews(summary) = prompt[BreakingNews](s"Write a summary of about 300 words given the provided context.") - println(summary) - -@main def runMarketNews(): Unit = conversation: - setContext(s"$currentDate Stock market results, rising stocks, falling stocks") - val news = prompt[MarketNews]("Write a short summary of the stock market results given the provided context.") - println(news) - -@main def runFermiEstimate(): Unit = conversation: - setContext("Estimate the number of medical needles in the world") - val Estimate(needlesInWorld) = prompt[Estimate]("Answer the question with an integer number given the provided context.") - println(s"Needles in world: $needlesInWorld") diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/UserQueries.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/UserQueries.scala deleted file mode 100644 index 711db19da..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/UserQueries.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.xebia.functional.xef.examples.scala.context.serpapi - -import com.xebia.functional.xef.reasoning.pdf.PDF -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -import scala.io.StdIn.readLine - -case class AIResponse(answer: String) derives SerialDescriptor, Decoder - -val PdfUrl = "https://people.cs.ksu.edu/~schmidt/705a/Scala/Programming-in-Scala.pdf" - -@main def runUserQueries(): Unit = conversation: - val pdf = PDF(openAI.DEFAULT_CHAT, openAI.DEFAULT_SERIALIZATION, summon[ScalaConversation]) - addContext(Array(pdf.readPDFFromUrl.readPDFFromUrl(PdfUrl).get)) - while (true) - println("Enter your question: ") - val AIResponse(answer) = prompt[AIResponse](readLine()) - println(s"$answer\n---\n") diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/images/HybridCity.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/images/HybridCity.scala deleted file mode 100644 index 1c9a3b4a9..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/images/HybridCity.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.xebia.functional.xef.examples.scala.images - -import com.xebia.functional.xef.prompt.Prompt -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -@main def runHybridCity(): Unit = conversation: - val imageUrls = images(Prompt("A hybrid city of Cádiz, Spain and Seattle, US.")) - println(imageUrls) diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/AnimalStory.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/AnimalStory.scala deleted file mode 100644 index 19e6d433e..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/AnimalStory.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.xebia.functional.xef.examples.scala.iteration - -import com.xebia.functional.xef.prompt.JvmPromptBuilder -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -case class Animal(name: String, habitat: String, diet: String) derives SerialDescriptor, Decoder -case class Invention(name: String, inventor: String, year: Int, purpose: String) derives SerialDescriptor, Decoder - -@main def runAnimalStory(): Unit = conversation: - val animal = prompt[Animal]("A unique animal species") - val invention = prompt[Invention]("A groundbreaking invention from the 20th century.") - println(s"Animal: $animal") - println(s"Invention: $invention") - val builder = new JvmPromptBuilder() - .addSystemMessage("You are a writer for a science fiction magazine.") - .addUserMessage("Write a short story of 200 words that involves the animal and the invention.") - val story = promptMessage(builder.build) - println(story) diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/ChessGame.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/ChessGame.scala deleted file mode 100644 index 42472e1a0..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/ChessGame.scala +++ /dev/null @@ -1,52 +0,0 @@ -package com.xebia.functional.xef.examples.scala.iteration - -import com.xebia.functional.xef.prompt.Prompt -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -import scala.annotation.tailrec - -case class ChessMove(player: String, move: String) derives SerialDescriptor, Decoder -case class ChessBoard(board: String) derives SerialDescriptor, Decoder -case class GameState(ended: Boolean = false, winner: Option[String] = None) derives SerialDescriptor, Decoder - -@tailrec -private def chessGame(moves: List[ChessMove] = Nil, gameState: GameState = new GameState)(using ScalaConversation): (String, ChessMove) = - if !gameState.ended then - val currentPlayer = if moves.size % 2 == 0 then "Player 1 (White)" else "Player 2 (Black)" - val previousMoves = moves.map(m => m.player + ":" + m.move).mkString(", ") - val movePrompt = moves match { - case Nil => s""" - |$currentPlayer, you are playing chess and it's your turn. - |Make your first move: - """.stripMargin - case l => s""" - |$currentPlayer, you are playing chess and it's your turn. - |Here are the previous moves: $previousMoves - |Make your next move: - """.stripMargin - } - println(movePrompt) - val move = prompt[ChessMove](movePrompt) - println(s"Move is: $move") - val boardPrompt = - s""" - |Given the following chess moves: $previousMoves, - |generate a chess board on a table with appropriate emoji representations for each move and piece. - |Add a brief description of the move and its implications. - """.stripMargin - val chessBoard = prompt[ChessBoard](boardPrompt) - println(s"Current board:\n${chessBoard.board}") - val gameStatePrompt = - s""" - |Given the following chess moves: ${moves.mkString(", ")}, - |has the game ended (win, draw, or stalemate)? - """.stripMargin - val gameState = prompt[GameState](gameStatePrompt) - chessGame(moves :+ move, gameState) - else (gameState.winner.getOrElse("Something went wrong"), moves.last) - -@main def runChessGame(): Unit = conversation: - val (winner, fMove) = chessGame() - println(s"Game over. Final move: $fMove, Winner: $winner") diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Annotated.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Annotated.scala deleted file mode 100644 index 168599987..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Annotated.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.xebia.functional.xef.examples.scala.serialization - -import com.xebia.functional.xef.prompt.Prompt -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -@Description("A book") -case class AnnotatedBook( - @Description("The name of the book") name: String, - @Description("The author of the book") author: String, - @Description("A 50 word paragraph with a summary of this book") summary: String -) derives SerialDescriptor, - Decoder - -@main def runAnnotatedBook(): Unit = conversation: - val book = prompt[AnnotatedBook]("To Kill a Mockingbird") - println(book) diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Simple.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Simple.scala deleted file mode 100644 index 2652fd8a3..000000000 --- a/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Simple.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.xebia.functional.xef.examples.scala.serialization - -import com.xebia.functional.xef.scala.conversation.* -import com.xebia.functional.xef.scala.serialization.* -import io.circe.Decoder - -case class AsciiArt(art: String) derives SerialDescriptor, Decoder - -case class Book(title: String, author: String, pages: Int) derives SerialDescriptor, Decoder - -case class City(population: Int, description: String) derives SerialDescriptor, Decoder - -case class Movie(title: String, genre: String, director: String) derives SerialDescriptor, Decoder - -case class Recipe(name: String, ingredients: List[String]) derives SerialDescriptor, Decoder - -@main def runAsciiArt(): Unit = conversation: - val AsciiArt(art) = prompt[AsciiArt]("ASCII art of a cat dancing") - println(art) - -@main def runBook(): Unit = conversation: - val topic = "functional programming" - val Book(title, author, pages) = prompt[Book](s"Give me the best-selling book about $topic") - println(s"The book $title is by $author and has $pages pages.") - -@main def runMovie(): Unit = conversation: - val Movie(title, genre, director) = prompt[Movie]("Inception movie genre and director.") - println(s"The movie $title is a $genre film directed by $director.") - -@main def runCities(): Unit = conversation: - val cadiz = prompt[City]("Cádiz, Spain") - val seattle = prompt[City]("Seattle, WA") - println(s"The population of Cádiz is ${cadiz.population} and the population of Seattle is ${seattle.population}.") - -@main def runRecipe(): Unit = conversation: - val Recipe(name, ingredients) = prompt[Recipe]("Recipe for chocolate chip cookies.") - println(s"The recipe for $name is $ingredients.") diff --git a/gradle.properties b/gradle.properties index a0979c287..e42fb3212 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ kotlin.native.cacheKind=none project.group=com.xebia pom.name=xef -pom.description=Building applications with LLMs through composability, in Kotlin and Scala +pom.description=Building applications with LLMs through composability in Kotlin pom.url=https://github.com/xebia-functional/xef pom.license.name=The Apache Software License, Version 2.0 pom.license.url=https://www.apache.org/licenses/LICENSE-2.0.txt @@ -21,5 +21,3 @@ systemProp.org.gradle.unsafe.kotlin.assignment=true # Workaround to disable Dokka setup from Arrow Gradle Config dokkaEnabled=false - -scalaVersion=3.3.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c36b5552..840ac3010 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,11 +20,6 @@ dokka = "1.9.10" logback = "1.4.11" node-gradle = "7.0.1" kotlinx-coroutines = "1.7.3" -scalaMultiversion = "2.0.6" -circe = "0.14.6" -catsEffect = "3.6-0142603" -munit = "0.7.29" -munitCatsEffect = "1.0.7" scrapeit = "1.1.5" rssreader = "3.5.0" lucene = "9.8.0" @@ -33,7 +28,6 @@ junit = "5.10.0" pdfbox = "3.0.0" mysql = "8.0.33" semverGradle = "0.5.0-rc.5" -scala = "3.3.1" openai-client-version = "3.5.1" gpt4all-java = "1.1.5" ai-djl = "0.24.0" @@ -104,11 +98,6 @@ hikari = { module = "com.zaxxer:HikariCP", version.ref = "hikari" } postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -circe = { module = "io.circe:circe-generic_3", version.ref = "circe" } -circe-parser = { module = "io.circe:circe-parser_3", version.ref = "circe" } -cats-effect = { module = "org.typelevel:cats-effect_3", version.ref = "catsEffect" } -munit-core = { module = "org.scalameta:munit_3", version.ref = "munit" } -munit-cats-effect = { module = "org.typelevel:munit-cats-effect-3_3", version.ref = "munitCatsEffect" } skrape = { module = "it.skrape:skrapeit", version.ref = "scrapeit" } skrape-browser-fetcher = { module = "it.skrape:skrapeit-browser-fetcher", version.ref = "scrapeit" } skrape-async-fetcher = { module = "it.skrape:skrapeit-asyn-fetcher", version.ref = "scrapeit" } @@ -118,7 +107,6 @@ lucene-queries = { module = "org.apache.lucene:lucene-queries", version.ref = "l assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } apache-pdf-box = { module = "org.apache.pdfbox:pdfbox", version.ref = "pdfbox" } jdbc-mysql-connector = { module = "mysql:mysql-connector-java", version.ref = "mysql" } -scala-lang = { module = "org.scala-lang:scala3-library_3", version.ref = "scala" } openai-client = { module = "com.aallam.openai:openai-client", version.ref = "openai-client-version" } gpt4all-java-bindings = { module = "com.hexadevlabs:gpt4all-java-binding", version.ref = "gpt4all-java" } ai-djl-huggingface-tokenizers = { module = "ai.djl.huggingface:tokenizers", version.ref = "ai-djl" } @@ -155,7 +143,6 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } arrow-gradle-nexus = { id = "io.arrow-kt.arrow-gradle-config-nexus", version.ref = "arrowGradle" } arrow-gradle-publish = { id = "io.arrow-kt.arrow-gradle-config-publish", version.ref = "arrowGradle" } -scala-multiversion = { id = "com.adtran.scala-multiversion-plugin", version.ref = "scalaMultiversion" } kotest-multiplatform = { id = "io.kotest.multiplatform", version.ref = "kotest" } semver-gradle = { id="com.javiersc.semver", version.ref="semverGradle" } suspend-transform-plugin = { id="love.forte.plugin.suspend-transform", version.ref="suspend-transform" } diff --git a/java/README.md b/java/README.md deleted file mode 100644 index 380f6937b..000000000 --- a/java/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# xef.ai for Java - -Build the project locally, from the project root: - -```shell -./gradlew build -``` - -## Java Spotless - -The Java module uses the [spotless](https://github.com/diffplug/spotless/tree/main/plugin-gradle#java) plugin. -Therefore, the previous command (`./gradlew build`) will fail in case there is any formatting issue. To apply format, you can run the following command: - -```shell -./gradlew spotlessApply -``` - -## Examples - -Check out some use case at the [Java examples](../examples/java) folder. - -### Running the Examples - -How to run the examples (from IntelliJ IDEA): - -* Set Env variable: `OPENAI_TOKEN=xxx` diff --git a/java/build.gradle.kts b/java/build.gradle.kts deleted file mode 100644 index 99ba21ab2..000000000 --- a/java/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - `java-library` - `maven-publish` - signing - `xef-java-publishing-conventions` - alias(libs.plugins.semver.gradle) - alias(libs.plugins.spotless) -} - -dependencies { - api(projects.xefCore) - api(projects.xefOpenai) - api(projects.xefPdf) - api(projects.xefSql) - api(libs.jdbc.mysql.connector) - api(libs.jackson) - api(libs.jackson.schema) - api(libs.jackson.schema.jakarta) - api(libs.jakarta.validation) - api(libs.kotlinx.coroutines.reactive) -} - -java { - withJavadocJar() - withSourcesJar() -} - -tasks.withType().configureEach { useJUnit() } - -tasks.withType { dependsOn(tasks.withType()) } diff --git a/java/src/main/java/com/xebia/functional/xef/java/auto/Empty.java b/java/src/main/java/com/xebia/functional/xef/java/auto/Empty.java deleted file mode 100644 index 82c339a6b..000000000 --- a/java/src/main/java/com/xebia/functional/xef/java/auto/Empty.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.xebia.functional.xef.java.auto; - -/** - * Module placeholder - */ -public class Empty { -} diff --git a/scala/.scalafmt.conf b/scala/.scalafmt.conf deleted file mode 100644 index 038d0ede0..000000000 --- a/scala/.scalafmt.conf +++ /dev/null @@ -1,36 +0,0 @@ -# tune this file as appropriate to your style! see: https://olafurpg.github.io/scalafmt/#Configuration - -version = "3.7.15" - -runner.dialect = "scala3" - -maxColumn = 150 - -continuationIndent.callSite = 2 - -newlines { - sometimesBeforeColonInMethodReturnType = false -} - -align { - arrowEnumeratorGenerator = false - ifWhileOpenParen = false - openParenCallSite = false - openParenDefnSite = false - tokens = ["%", "%%"] -} - -docstrings.style = Asterisk - -rewrite { - rules = [SortImports, RedundantBraces] - redundantBraces.maxLines = 1 -} - -optIn { - breaksInsideChains = true -} - -project.excludeFilters = [ - "metals.sbt" -] diff --git a/scala/README.md b/scala/README.md deleted file mode 100644 index e74c18873..000000000 --- a/scala/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# xef.ai for Scala - -Build the project locally, from the project root: - -```shell -./gradlew build -``` - -## Scalafmt - -The Scala module uses the [spotless](https://github.com/diffplug/spotless/tree/main/plugin-gradle#scala) plugin. -Therefore, the previous command (`./gradlew build`) will fail if there are any formatting issues. -To apply formatting, you can run the following command: - -```shell -./gradlew spotlessApply -``` - -## Examples - -Check out some use case at the [Scala examples](../examples/scala) folder. - -### Running the Examples - -How to run the examples (from IntelliJ IDEA): - -* Set Java version 20 or above -* Set VM options: `--enable-preview` (if using Java 20 specifically) -* Set Env variable: `OPENAI_TOKEN=xxx` diff --git a/scala/build.gradle.kts b/scala/build.gradle.kts deleted file mode 100644 index e17e0c4c4..000000000 --- a/scala/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - scala - `maven-publish` - signing - alias(libs.plugins.semver.gradle) - alias(libs.plugins.spotless) - `xef-scala-publishing-conventions` -} - -dependencies { - implementation(projects.xefCore) - implementation(projects.xefOpenai) - implementation(libs.kotlinx.coroutines.reactive) - // TODO split to separate Scala library - implementation(projects.xefPdf) - implementation(libs.circe.parser) - implementation(libs.circe) - implementation(libs.scala.lang) - implementation(libs.logback) - testImplementation(libs.munit.core) -} - -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - toolchain { languageVersion = JavaLanguageVersion.of(21) } - withSourcesJar() -} - -tasks.withType().configureEach { useJUnit() } - -tasks.withType { - scalaCompileOptions.additionalParameters = listOf("-Wunused:all", "-Wvalue-discard") -} - -spotless { scala { scalafmt("3.7.15").configFile(".scalafmt.conf") } } diff --git a/scala/src/main/resources/logback.xml b/scala/src/main/resources/logback.xml deleted file mode 100644 index 8b62c8b02..000000000 --- a/scala/src/main/resources/logback.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n - - - - - - - - - - - - - - - diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/package.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/package.scala deleted file mode 100644 index d65b83efa..000000000 --- a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/package.scala +++ /dev/null @@ -1,65 +0,0 @@ -package com.xebia.functional.xef.scala.conversation - -import com.xebia.functional.xef.conversation.* -import com.xebia.functional.xef.conversation.llm.openai.* -import com.xebia.functional.xef.conversation.llm.openai.OpenAI.FromEnvironment.* -import com.xebia.functional.xef.llm.* -import com.xebia.functional.xef.llm.models.images.* -import com.xebia.functional.xef.metrics.* -import com.xebia.functional.xef.prompt.Prompt -import com.xebia.functional.xef.scala.serialization.* -import com.xebia.functional.xef.store.* -import io.circe.Decoder -import io.circe.parser.parse -import org.reactivestreams.* - -import java.util.UUID.* -import java.util.concurrent.LinkedBlockingQueue -import scala.jdk.CollectionConverters.* - -class ScalaConversation(store: VectorStore, metric: Metric, conversationId: ConversationId) extends JVMConversation(store, metric, conversationId) - -def addContext(context: Array[String])(using conversation: ScalaConversation): Unit = - conversation.addContextFromArray(context).join() - -def prompt[A: Decoder: SerialDescriptor](prompt: Prompt, chat: ChatWithFunctions = DEFAULT_SERIALIZATION)(using conversation: ScalaConversation): A = - conversation.prompt(chat, prompt, chat.chatFunction(SerialDescriptor[A].serialDescriptor), fromJson).join() - -def promptMessage(prompt: Prompt, chat: Chat = DEFAULT_CHAT)(using conversation: ScalaConversation): String = - conversation.promptMessage(chat, prompt).join() - -def promptMessages(prompt: Prompt, chat: Chat = DEFAULT_CHAT)(using conversation: ScalaConversation): List[String] = - conversation.promptMessages(chat, prompt).join().asScala.toList - -def promptStreaming(prompt: Prompt, chat: Chat = DEFAULT_CHAT)(using conversation: ScalaConversation): LazyList[String] = - val publisher = conversation.promptStreamingToPublisher(chat, prompt) - val queue = new LinkedBlockingQueue[String]() - publisher.subscribe(new Subscriber[String]: // TODO change to fs2 or similar - def onSubscribe(s: Subscription): Unit = s.request(Long.MaxValue) - def onNext(t: String): Unit = queue.add(t); () - def onError(t: Throwable): Unit = throw t - def onComplete(): Unit = () - ) - LazyList.continually(queue.take) - -def prompt[A: Decoder: SerialDescriptor](message: String)(using ScalaConversation): A = - prompt(Prompt(message)) - -def promptMessage(message: String)(using ScalaConversation): String = - promptMessage(Prompt(message)) - -def promptMessages(message: String)(using ScalaConversation): List[String] = - promptMessages(Prompt(message)) - -def promptStreaming(message: String)(using ScalaConversation): LazyList[String] = - promptStreaming(Prompt(message)) - -def images(prompt: Prompt, images: Images = DEFAULT_IMAGES, numberImages: Int = 1, size: String = "1024x1024")(using - conversation: ScalaConversation -): ImagesGenerationResponse = - conversation.images(images, prompt, numberImages, size).join() - -def conversation[A](block: ScalaConversation ?=> A, id: ConversationId = ConversationId(randomUUID.toString)): A = - block(using ScalaConversation(LocalVectorStore(DEFAULT_EMBEDDING), LogsMetric(), id)) - -private def fromJson[A: Decoder]: FromJson[A] = json => parse(json).flatMap(Decoder[A].decodeJson(_)).fold(throw _, identity) diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/KotlinXSerializers.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/KotlinXSerializers.scala deleted file mode 100644 index 30aa05e4a..000000000 --- a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/KotlinXSerializers.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.xebia.functional.xef.scala.serialization - -import kotlin.jvm.internal.* -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.BuiltinSerializersKt -import kotlinx.serialization.builtins.BuiltinSerializersKt.serializer - -object KotlinXSerializers: - val int: KSerializer[java.lang.Integer] = serializer(IntCompanionObject.INSTANCE) - val string: KSerializer[String] = serializer(StringCompanionObject.INSTANCE) - val boolean: KSerializer[java.lang.Boolean] = serializer(BooleanCompanionObject.INSTANCE) - val double: KSerializer[java.lang.Double] = serializer(DoubleCompanionObject.INSTANCE) - val float: KSerializer[java.lang.Float] = serializer(FloatCompanionObject.INSTANCE) - val long: KSerializer[java.lang.Long] = serializer(LongCompanionObject.INSTANCE) - val short: KSerializer[java.lang.Short] = serializer(ShortCompanionObject.INSTANCE) - val byte: KSerializer[java.lang.Byte] = serializer(ByteCompanionObject.INSTANCE) - val char: KSerializer[Character] = serializer(CharCompanionObject.INSTANCE) - val unit: KSerializer[kotlin.Unit] = serializer(kotlin.Unit.INSTANCE) diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala deleted file mode 100644 index 9b574065a..000000000 --- a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala +++ /dev/null @@ -1,116 +0,0 @@ -package com.xebia.functional.xef.scala.serialization - -import com.xebia.functional.xef.conversation.jvm.Description as JvmDescription -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.{SerialDescriptor as KtSerialDescriptor, SerialKind, StructureKind} -import kotlinx.serialization.encoding.{Decoder as KtDecoder, Encoder as KtEncoder} - -import java.lang.annotation.Annotation -import java.util -import scala.annotation.meta.* -import scala.compiletime.* -import scala.deriving.* -import scala.jdk.CollectionConverters.* -import scala.quoted.* -import scala.reflect.ClassTag - -trait SerialDescriptor[A]: - def serialDescriptor: KtSerialDescriptor - def kserializer: KSerializer[A] = new KSerializer[A]: - override def getDescriptor: KtSerialDescriptor = serialDescriptor - override def serialize(encoder: KtEncoder, t: A): Unit = ??? // TODO should we implement this? - override def deserialize(decoder: KtDecoder): A = ??? // TODO should we implement this? - -object SerialDescriptor extends SerialDescriptorInstances: - def apply[A](using ev: SerialDescriptor[A]): SerialDescriptor[A] = ev - - private inline def getElemsLabel[T <: Tuple]: List[String] = inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (h *: t) => erasedValue[h].toString :: getElemsLabel[t] - - private inline def getSerialDescriptor[T <: Tuple]: List[KtSerialDescriptor] = inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (String *: t) => KotlinXSerializers.string.getDescriptor :: getSerialDescriptor[t] - case _: (Boolean *: t) => KotlinXSerializers.boolean.getDescriptor :: getSerialDescriptor[t] - case _: (Byte *: t) => KotlinXSerializers.byte.getDescriptor :: getSerialDescriptor[t] - case _: (Char *: t) => KotlinXSerializers.char.getDescriptor :: getSerialDescriptor[t] - case _: (Double *: t) => KotlinXSerializers.double.getDescriptor :: getSerialDescriptor[t] - case _: (Float *: t) => KotlinXSerializers.float.getDescriptor :: getSerialDescriptor[t] - case _: (Int *: t) => KotlinXSerializers.int.getDescriptor :: getSerialDescriptor[t] - case _: (Long *: t) => KotlinXSerializers.long.getDescriptor :: getSerialDescriptor[t] - case _: (Short *: t) => KotlinXSerializers.short.getDescriptor :: getSerialDescriptor[t] - case _: (Unit *: t) => KotlinXSerializers.unit.getDescriptor :: getSerialDescriptor[t] - case _: (h *: t) => summonInline[SerialDescriptor[h]].serialDescriptor :: getSerialDescriptor[t] - - inline final def derived[A](using m: Mirror.Of[A]): SerialDescriptor[A] = - val fieldAnnotationMap = getFieldAnnotationsMap[A] - new SerialDescriptor[A]: - val serialDescriptorImpl: KtSerialDescriptor = new KtSerialDescriptor: - val labels = getElemsLabel[m.MirroredElemLabels] - val serialDescriptors = getSerialDescriptor[m.MirroredElemTypes] - - override def getElementIndex(name: String): Int = labels.indexOf(name) - - // We're going to ignore annotations for now, it's not relevant for JsonSchema - override def getElementAnnotations(index: Int): util.List[Annotation] = - val fieldName = labels(index) - val annotations = fieldAnnotationMap(fieldName) - annotations.filter(_.isInstanceOf[Description]).asJava.asInstanceOf[util.List[Annotation]] - - override def getElementDescriptor(index: Int): KtSerialDescriptor = serialDescriptors(index) - - // We're going to ignore annotations for now, it's not relevant for JsonSchema - override def getAnnotations: util.List[Annotation] = - val annotations = getStaticAnnotations[A] - annotations.filter(_.isInstanceOf[Description]).asJava.asInstanceOf[util.List[Annotation]] - - override def getElementsCount: Int = labels.size - - override def isInline: Boolean = false - - // Is the element wrapped in `Option`, or is a union with `Null`? - override def isNullable: Boolean = false - - override def getKind: SerialKind = StructureKind.CLASS.INSTANCE - - override def getSerialName: String = constValue[m.MirroredLabel] - - override def getElementName(i: Int): String = labels(i) - - // Does the element at the given index have a default value, or is it wrapped in `Option`, or is a union with `Null`? - override def isElementOptional(index: Int): Boolean = false - - def serialDescriptor: KtSerialDescriptor = serialDescriptorImpl - -inline def getStaticAnnotations[A]: List[Any] = ${ getAnnotationsImpl[A] } - -def getAnnotationsImpl[A: Type](using Quotes): Expr[List[Any]] = - import quotes.reflect.* - val annotations = TypeRepr.of[A].typeSymbol.annotations.map(_.asExpr) - Expr.ofList(annotations) - -inline def getFieldAnnotationsMap[A]: Map[String, List[Any]] = - ${ getFieldAnnotationsMapImpl[A] } - -def getFieldAnnotationsMapImpl[A: Type](using Quotes): Expr[Map[String, List[Any]]] = - import quotes.reflect.* - // Extract the fields from the type - val fields = TypeRepr.of[A].typeSymbol.primaryConstructor.paramSymss.flatten - // For each field, extract its name and annotations - val fieldAnnotationsMap: List[Expr[(String, List[Any])]] = fields.map: field => - val fieldName = Expr(field.name) - val annotations = Expr.ofList(field.annotations.map(_.asExpr)) - '{ ($fieldName, $annotations) } - // Convert list of (String, List[Any]) tuples to a map - fieldAnnotationsMap match - case Nil => '{ Map.empty[String, List[Any]] } - case _ => - val mapExpr = Expr.ofList(fieldAnnotationsMap) - '{ $mapExpr.toMap } - -/** - * A scala annotation that maps to the jvm description annotation - */ -class Description(description: String) extends JvmDescription with scala.annotation.StaticAnnotation: - override def value(): String = description - override def annotationType(): Class[_ <: Annotation] = classOf[JvmDescription] diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala deleted file mode 100644 index ee761f27c..000000000 --- a/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala +++ /dev/null @@ -1,64 +0,0 @@ -package com.xebia.functional.xef.scala.serialization - -import kotlin.jvm.internal.Reflection -import kotlin.reflect.KClass -import kotlinx.serialization.builtins.BuiltinSerializersKt - -import scala.compiletime.summonInline -import scala.reflect.ClassTag - -class SerialDescriptorInstances: - - given [T: SerialDescriptor]: SerialDescriptor[Option[T]] = new SerialDescriptor[Option[T]]: - def serialDescriptor = BuiltinSerializersKt.getNullable(SerialDescriptor[T].kserializer).getDescriptor - - given [T: ClassTag: SerialDescriptor]: SerialDescriptor[Array[T]] = new SerialDescriptor[Array[T]]: - def serialDescriptor = - val kClass = Reflection.createKotlinClass(summonInline[ClassTag[T]].runtimeClass).asInstanceOf[KClass[T]] - BuiltinSerializersKt.ArraySerializer(kClass, SerialDescriptor[T].kserializer).getDescriptor - - given [T: SerialDescriptor]: SerialDescriptor[List[T]] = new SerialDescriptor[List[T]]: - def serialDescriptor = BuiltinSerializersKt.ListSerializer(SerialDescriptor[T].kserializer).getDescriptor - - given [T: SerialDescriptor]: SerialDescriptor[Seq[T]] = new SerialDescriptor[Seq[T]]: - def serialDescriptor = BuiltinSerializersKt.ListSerializer(SerialDescriptor[T].kserializer).getDescriptor - - given [T: SerialDescriptor]: SerialDescriptor[Vector[T]] = new SerialDescriptor[Vector[T]]: - def serialDescriptor = BuiltinSerializersKt.ListSerializer(SerialDescriptor[T].kserializer).getDescriptor - - given [T: SerialDescriptor]: SerialDescriptor[Set[T]] = new SerialDescriptor[Set[T]]: - def serialDescriptor = BuiltinSerializersKt.SetSerializer(SerialDescriptor[T].kserializer).getDescriptor - - given [K: SerialDescriptor, V: SerialDescriptor]: SerialDescriptor[Map[K, V]] = new SerialDescriptor[Map[K, V]]: - def serialDescriptor = - BuiltinSerializersKt.MapSerializer(SerialDescriptor[K].kserializer, SerialDescriptor[V].kserializer).getDescriptor - - given SerialDescriptor[Boolean] = new SerialDescriptor[Boolean]: - def serialDescriptor = KotlinXSerializers.boolean.getDescriptor - - given SerialDescriptor[Byte] = new SerialDescriptor[Byte]: - def serialDescriptor = KotlinXSerializers.byte.getDescriptor - - given SerialDescriptor[Char] = new SerialDescriptor[Char]: - def serialDescriptor = KotlinXSerializers.char.getDescriptor - - given SerialDescriptor[Double] = new SerialDescriptor[Double]: - def serialDescriptor = KotlinXSerializers.double.getDescriptor - - given SerialDescriptor[Float] = new SerialDescriptor[Float]: - def serialDescriptor = KotlinXSerializers.float.getDescriptor - - given SerialDescriptor[Int] = new SerialDescriptor[Int]: - def serialDescriptor = KotlinXSerializers.int.getDescriptor - - given SerialDescriptor[Long] = new SerialDescriptor[Long]: - def serialDescriptor = KotlinXSerializers.long.getDescriptor - - given SerialDescriptor[Short] = new SerialDescriptor[Short]: - def serialDescriptor = KotlinXSerializers.short.getDescriptor - - given SerialDescriptor[String] = new SerialDescriptor[String]: - def serialDescriptor = KotlinXSerializers.string.getDescriptor - - given SerialDescriptor[Unit] = new SerialDescriptor[Unit]: - def serialDescriptor = KotlinXSerializers.unit.getDescriptor diff --git a/scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala b/scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala deleted file mode 100644 index 4c103b6e1..000000000 --- a/scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala +++ /dev/null @@ -1,68 +0,0 @@ -package com.xebia.functional.xef.scala.serialization - -import cats.syntax.either.* -import kotlinx.serialization.builtins.BuiltinSerializersKt -import munit.FunSuite - -import scala.collection.immutable.HashSet -import scala.compiletime.summonInline -import scala.reflect.ClassTag - -class SerialDescriptorSpec extends FunSuite: - - test("Should create a SerialDescriptor for a simple case class") { - final case class Person(age: Int, name: String) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with optional fields") { - final case class Person(age: Int, name: String, id: Option[Long]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with set and list fields") { - final case class Person(age: Int, name: String, siblingNames: Set[String], nationality: List[String]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with a list case class") { - final case class Pet(age: Int, name: String) derives SerialDescriptor - final case class Person(age: Int, name: String, pets: List[Pet]) derives SerialDescriptor - - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with seq and vector fields") { - final case class Person(age: Int, name: String, other1: Seq[Byte], other2: Vector[Short]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with an array field") { - final case class Person(other: Array[Double]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with map fields") { - final case class Person(age: Int, name: String, alias: Map[String, String]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a simple case class with HashSet field, providing a custom given") { - given [T: SerialDescriptor]: SerialDescriptor[HashSet[T]] = new SerialDescriptor[HashSet[T]]: - def serialDescriptor = BuiltinSerializersKt.SetSerializer(SerialDescriptor[T].kserializer).getDescriptor - final case class Person(age: Int, name: String, alias: HashSet[String]) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a composite case class") { - final case class Person(age: Int, name: PersonName) derives SerialDescriptor - final case class PersonName(firstName: String, lastName: String) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } - - test("Should create a SerialDescriptor for a composite (level 2) case class") { - final case class Person(age: Int, placeOfBirth: PlaceOfBirth) derives SerialDescriptor - final case class PlaceOfBirth(city: String, country: Country) derives SerialDescriptor - final case class Country(country: String) derives SerialDescriptor - assert(Either.catchNonFatal(SerialDescriptor[Person].serialDescriptor).isRight) - } diff --git a/settings.gradle.kts b/settings.gradle.kts index aaaf34756..5842a6bca 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -68,30 +68,9 @@ include("xef-kotlin-examples") project(":xef-kotlin-examples").projectDir = file("examples/kotlin") // -// -include("xef-scala-examples") -project(":xef-scala-examples").projectDir = file("examples/scala") - -include("xef-scala") -project(":xef-scala").projectDir = file("scala") -// - -// -include("xef-java") -project(":xef-java").projectDir = file("java") - -include("xef-java-examples") -project(":xef-java-examples").projectDir = file("examples/java") -// - -// include("xef-reasoning") project(":xef-reasoning").projectDir = file("reasoning") -include("xef-java-examples") -project(":xef-java-examples").projectDir = file("examples/java") -// - // include("xef-server") project(":xef-server").projectDir = file("server")