diff --git a/README.md b/README.md
index fc9f1c63b..bbbb5af8a 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,7 @@ in your build, if you haven't done it before.
## đź“– Quick Introduction
-In this small introduction we look at the main features of xef, including the `ai` function.
+In this small introduction we look at the main features of xef, including the `conversation` function.
- [ Kotlin version](https://github.com/xebia-functional/xef/blob/main/docs/intro/kotlin.md)
- [ Scala version](https://github.com/xebia-functional/xef/blob/main/docs/intro/scala.md)
@@ -121,5 +121,5 @@ In this small introduction we look at the main features of xef, including the `a
You can also have a look at the examples to have a feeling of how using the library looks like.
- [ Examples in Kotlin](https://github.com/xebia-functional/xef/tree/main/examples/kotlin/src/main/kotlin/com/xebia/functional/xef/conversation)
-- [ Examples in Scala](https://github.com/xebia-functional/xef/tree/main/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation)
+- [ Examples in Scala](https://github.com/xebia-functional/xef/tree/main/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala)
- [ 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/docs/intro/scala.md b/docs/intro/scala.md
index ca47d3c19..dd5be1a56 100644
--- a/docs/intro/scala.md
+++ b/docs/intro/scala.md
@@ -1,51 +1,19 @@
# 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 `ai` function, which is your gate to the modern AI world.
+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.
+(Large Language Model). The easiest way is to just get the information back as a string or list of strings.
-```scala
-import com.xebia.functional.xef.scala.auto.*
+```scala 3
+import com.xebia.functional.xef.scala.conversation.*
-@main def runBook: Unit = ai {
- val topic: String = "functional programming"
- promptMessage(s"Give me a selection of books about $topic")
-}.getOrElse(ex => println(ex.getMessage))
-```
-
-In the example above we _execute_ the `ai` block with `getOrElse`, so in case an exception
-is thrown (for example, if your API key is not correct), we are handing the error by printing
-the reason of the error.
-
-In the next examples we'll write functions that rely on `ai`'s DSL functionality,
-but without actually extracting the values yet using `getOrThrow` or `getOrElse`.
-We'll eventually call this functions from an `ai` block as we've shown above, and
-this allows us to build larger pipelines, and only extract the final result at the end.
-
-This can be done by either using a context parameters or function _using_ `AIScope`.
-Let's compare the two:
-
-```scala
-def book(topic: String)(using scope: AIScope): List[String] =
- promptMessage(s"Give me a selection of books about $topic")
-
-def book(topic: String): AIScope ?=> List[String] =
- promptMessage(s"Give me a selection of books about $topic")
-```
-
-Using the type alias `AI`, defined in `com.xebia.functional.xef.scala.auto` as:
-
-```scala
-type AI[A] = AIScope ?=> A
-```
-
-book function can be written in this way:
-
-```scala
-def book(topic: String): AI[List[String]] =
- promptMessage(s"Give me a selection of books about $topic")
+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
@@ -54,7 +22,7 @@ If the code above fails, you may need to perform some additional setup.
### OpenAI
-By default, the `ai` block connects to [OpenAI](https://platform.openai.com/).
+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.
@@ -102,28 +70,29 @@ strings we obtain. Fortunately, you can also ask xef.ai to give you back the inf
using a _custom type_. The library takes care of instructing the LLM on building such
a structure, and deserialize the result back for you.
-```scala
-import com.xebia.functional.xef.scala.auto.*
+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
-import io.circe.parser.decode
-private final case class Book(name: String, author: String, summary: String) derives SerialDescriptor, Decoder
+case class Book(name: String, author: String, pages: Int) derives SerialDescriptor, Decoder
+```
-def summarizeBook(title: String, author: String)(using scope: AIScope): Book =
- prompt(s"$title by $author summary.")
+The `conversation` block can then be written in this way:
-@main def runBook: Unit =
- ai {
- val toKillAMockingBird = summarizeBook("To Kill a Mockingbird", "Harper Lee")
- println(s"${toKillAMockingBird.name} by ${toKillAMockingBird.author} summary:\n ${toKillAMockingBird.summary}")
- }.getOrElse(ex => println(ex.getMessage))
+```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 it's based on Kotlin. Hence, the core
+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 on each field based on its name (like `title` and `author` above).
+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
@@ -133,37 +102,45 @@ 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.
+ 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 a web
-search service to enrich that context.
+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
-```scala
-import com.xebia.functional.xef.scala.agents.DefaultSearch
-import com.xebia.functional.xef.scala.auto.*
+val openAI: OpenAI = OpenAI.FromEnvironment
-private def getQuestionAnswer(question: String)(using scope: AIScope): List[String] =
- contextScope(DefaultSearch.search("Weather in Cádiz, Spain")) {
- promptMessage(question)
- }
+def setContext(query: String)(using conversation: ScalaConversation): Unit =
+ addContext(Search(openAI.DEFAULT_CHAT, conversation, 3).search(query).get)
-@main def runWeather: Unit = ai {
+@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?"
- println(getQuestionAnswer(question).mkString("\n"))
-}.getOrElse(ex => println(ex.getMessage))
+ 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 it provides maximum
+> 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/auto) for a complete list of different use cases.
+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/scala/build.gradle.kts b/examples/scala/build.gradle.kts
index 3ce0f1895..a88198b95 100644
--- a/examples/scala/build.gradle.kts
+++ b/examples/scala/build.gradle.kts
@@ -16,7 +16,7 @@ java {
dependencies {
implementation(projects.xefCore)
implementation(projects.xefScala)
- implementation(projects.xefReasoning)
+ implementation(projects.xefReasoning)
implementation(projects.xefOpenai)
implementation(libs.circe.parser)
implementation(libs.scala.lang)
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
new file mode 100644
index 000000000..c9f4b17d1
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/Simple.scala
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 000000000..fbe40da7e
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/Simple.scala
@@ -0,0 +1,45 @@
+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
new file mode 100644
index 000000000..711db19da
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/context/serpapi/UserQueries.scala
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 000000000..1c9a3b4a9
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/images/HybridCity.scala
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 000000000..19e6d433e
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/AnimalStory.scala
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 000000000..42472e1a0
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/iteration/ChessGame.scala
@@ -0,0 +1,52 @@
+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
new file mode 100644
index 000000000..168599987
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Annotated.scala
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 000000000..2652fd8a3
--- /dev/null
+++ b/examples/scala/src/main/scala/com/xebia/functional/xef/examples/scala/serialization/Simple.scala
@@ -0,0 +1,37 @@
+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/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/BreakingNews.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/BreakingNews.scala
deleted file mode 100644
index 10316ae6d..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/BreakingNews.scala
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.contexts
-
-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 com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-import java.text.SimpleDateFormat
-import java.util.Date
-
-private final case class BreakingNewsAboutCovid(summary: String) derives SerialDescriptor, Decoder
-
-@main def runBreakingNews: Unit =
- conversation {
- val sdf = SimpleDateFormat("dd/M/yyyy")
- val currentDate = sdf.format(Date())
- val search = Search(OpenAI.FromEnvironment.DEFAULT_CHAT, summon[ScalaConversation], 3)
- addContext(search.search(s"$currentDate Covid News").get())
- val news = prompt[BreakingNewsAboutCovid](Prompt(s"Write a paragraph of about 300 words about: $currentDate Covid News"))
- println(news.summary)
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/DivergentTasks.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/DivergentTasks.scala
deleted file mode 100644
index bd842f932..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/DivergentTasks.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.contexts
-
-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 com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class NumberOfMedicalNeedlesInWorld(numberOfNeedles: Long) derives SerialDescriptor, Decoder
-
-@main def runDivergentTasks: Unit =
- conversation {
- val search = Search(OpenAI.FromEnvironment.DEFAULT_CHAT, summon[ScalaConversation], 3)
- addContext(search.search("Estimate amount of medical needles in the world").get())
- val needlesInWorld = prompt[NumberOfMedicalNeedlesInWorld](Prompt("Provide the number of medical needles in the world as an integer number"))
- println(s"Needles in world: ${needlesInWorld.numberOfNeedles}")
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Markets.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Markets.scala
deleted file mode 100644
index 3eb8a0814..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Markets.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.contexts
-
-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 com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-import java.text.SimpleDateFormat
-import java.util.Date
-
-private final case class MarketNews(news: String, raisingStockSymbols: List[String], decreasingStockSymbols: List[String])
- derives SerialDescriptor,
- Decoder
-
-@main def runMarkets: Unit =
- conversation {
- val sdf = SimpleDateFormat("dd/M/yyyy")
- val currentDate = sdf.format(Date())
- val search = Search(OpenAI.FromEnvironment.DEFAULT_CHAT, summon[ScalaConversation], 3)
- addContext(search.search(s"$currentDate Stock market results, raising stocks, decreasing stocks").get())
- val news = prompt[MarketNews](Prompt("Write a short summary of the stock market results given the provided context."))
- println(news)
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/PDFDocument.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/PDFDocument.scala
deleted file mode 100644
index 2aa1d5bf8..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/PDFDocument.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.contexts
-
-import com.xebia.functional.xef.conversation.llm.openai.OpenAI
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.reasoning.pdf.PDF
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-import scala.io.StdIn.readLine
-
-private final case class AIResponse(answer: String) derives SerialDescriptor, Decoder
-
-val pdfUrl = "https://people.cs.ksu.edu/~schmidt/705a/Scala/Programming-in-Scala.pdf"
-
-@main def runPDFDocument: Unit =
- conversation {
- val pdf = PDF(OpenAI.FromEnvironment.DEFAULT_CHAT, OpenAI.FromEnvironment.DEFAULT_SERIALIZATION, summon[ScalaConversation])
- addContext(Array(pdf.readPDFFromUrl.readPDFFromUrl(pdfUrl).get()))
- while (true) {
- println("Enter your question: ")
- val line = scala.io.StdIn.readLine()
- val response = prompt[AIResponse](Prompt(line))
- println(s"${response.answer}\n---\n")
- }
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Weather.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Weather.scala
deleted file mode 100644
index 453700db7..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/contexts/Weather.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.xebia.functional.xef.scala.conversation
-
-import com.xebia.functional.xef.scala.conversation.*
-import com.xebia.functional.xef.conversation.llm.openai.OpenAI
-import com.xebia.functional.xef.reasoning.serpapi.Search
-import com.xebia.functional.xef.prompt.Prompt
-
-private def getQuestionAnswer(question: Prompt)(using conversation: ScalaConversation): String =
- val search: Search = Search(OpenAI.FromEnvironment.DEFAULT_CHAT, conversation, 3)
- addContext(search.search("Weather in Cádiz, Spain").get())
- promptMessage(question)
-
-@main def runWeather: Unit =
- val question = Prompt("Knowing this forecast, what clothes do you recommend I should wear if I live in Cádiz?")
- println(conversation(getQuestionAnswer(question)).mkString("\n"))
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/conversations/Animal.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/conversations/Animal.scala
deleted file mode 100644
index 4f3405bb2..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/conversations/Animal.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.conversations
-
-import com.xebia.functional.xef.prompt.{JvmPromptBuilder, Prompt}
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class Animal(name: String, habitat: String, diet: String) derives SerialDescriptor, Decoder
-
-private final case class Invention(name: String, inventor: String, year: Int, purpose: String) derives SerialDescriptor, Decoder
-
-@main def runAnimal: Unit =
- conversation {
- val animal: Animal = prompt(Prompt("A unique animal species"))
- val invention: Invention = prompt(Prompt("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/scala/conversation/fields/Book.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/fields/Book.scala
deleted file mode 100644
index 15f278c5f..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/fields/Book.scala
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.fields
-
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-@Description("A book")
-case class Book(
- @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
-
-def summarizeBook(title: String, author: String)(using conversation: ScalaConversation): Book =
- prompt(Prompt(s"$title by $author summary."))
-
-@main def runBook: Unit =
- conversation {
- val toKillAMockingBird = summarizeBook("To Kill a Mockingbird", "Harper Lee")
- println(s"${toKillAMockingBird.name} by ${toKillAMockingBird.author} summary:\n ${toKillAMockingBird.summary}")
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/image/Population.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/image/Population.scala
deleted file mode 100644
index 62dc2b1c4..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/image/Population.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.image
-
-import com.xebia.functional.xef.llm.models.images.ImagesGenerationResponse
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class Population(size: Int, description: String) derives SerialDescriptor, Decoder
-
-private final case class Image(description: String, url: String) derives SerialDescriptor, Decoder
-
-@main def runPopulation: Unit =
- conversation {
- val cadiz: Population = prompt(Prompt("Population of Cádiz, Spain."))
- val seattle: Population = prompt(Prompt("Population of Seattle, WA."))
- val imgs: ImagesGenerationResponse = images(Prompt("A hybrid city of Cádiz, Spain and Seattle, US."))
- println(imgs)
- println(s"The population of Cádiz is ${cadiz.size} and the population of Seattle is ${seattle.size}")
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ASCIIArt.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ASCIIArt.scala
deleted file mode 100644
index ea963b93c..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ASCIIArt.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.serialization
-
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class ASCIIArt(art: String) derives SerialDescriptor, Decoder
-
-@main def runASCIIArt: Unit =
- lazy val asciiArt = conversation {
- prompt[ASCIIArt](Prompt("ASCII art of a cat dancing"))
- }
- println(asciiArt.art)
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ChessAI.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ChessAI.scala
deleted file mode 100644
index d4dde7858..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/ChessAI.scala
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.serialization
-
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-import scala.annotation.tailrec
-
-private final case class ChessMove(player: String, move: String) derives SerialDescriptor, Decoder
-
-private final case class ChessBoard(board: String) derives SerialDescriptor, Decoder
-
-private final case class GameState(ended: Boolean, winner: Option[String]) derives SerialDescriptor, Decoder
-
-@tailrec
-private def chessGame(moves: List[ChessMove], gameState: GameState)(using conversation: 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.
- |These are no previous chess board moves.
- |Make your first move:
- """.stripMargin
- case l => s"""
- |$currentPlayer, you are playing chess and it's your turn.
- |These are the previous chess board moves: $previousMoves
- |Make your next move:
- """.stripMargin
- }
- println(movePrompt)
- val move: ChessMove = prompt(Prompt(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 it's implications
- """.stripMargin
-
- val chessBoard: ChessBoard = prompt(Prompt(boardPrompt))
- println(s"Current board:\n${chessBoard.board}")
-
- val gameStatePrompt =
- Prompt(s"""
- |Given the following chess moves: ${moves.mkString(", ")},
- |has the game ended (win, draw, or stalemate)?
- """.stripMargin)
-
- val gameState: GameState = prompt(gameStatePrompt)
-
- chessGame(moves :+ move, gameState)
- else (gameState.winner.getOrElse("Something went wrong"), moves.last)
-
-@main def runChess: Unit =
- conversation {
- val (winner, fMove) = chessGame(Nil, GameState(false, None))
- println(s"Game over. Final move: $fMove, Winner: $winner")
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Movie.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Movie.scala
deleted file mode 100644
index 6eb070589..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Movie.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.serialization
-
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class Movie(title: String, genre: String, director: String) derives SerialDescriptor, Decoder
-
-@main def runMovie: Unit =
- conversation {
- val movie = prompt[Movie](Prompt("Inception movie genre and director."))
- println(s"The movie ${movie.title} is a ${movie.genre} film directed by ${movie.director}.")
- }
diff --git a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Recipe.scala b/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Recipe.scala
deleted file mode 100644
index f548c49f2..000000000
--- a/examples/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/serialization/Recipe.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.xebia.functional.xef.scala.conversation.serialization
-
-import com.xebia.functional.xef.prompt.Prompt
-import com.xebia.functional.xef.scala.conversation.*
-import io.circe.Decoder
-
-private final case class Recipe(name: String, ingredients: List[String]) derives SerialDescriptor, Decoder
-
-@main def runRecipe: Unit =
- conversation {
- val recipe: Recipe = prompt(Prompt("Recipe for chocolate chip cookies."))
- println(s"The recipe for ${recipe.name} is ${recipe.ingredients}")
- }
diff --git a/gradle.properties b/gradle.properties
index 9400b3a33..a0979c287 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,4 +22,4 @@ systemProp.org.gradle.unsafe.kotlin.assignment=true
# Workaround to disable Dokka setup from Arrow Gradle Config
dokkaEnabled=false
-scalaVersion=3.3.0
+scalaVersion=3.3.1
diff --git a/scala/README.md b/scala/README.md
index 611295003..c04cd2a66 100644
--- a/scala/README.md
+++ b/scala/README.md
@@ -9,7 +9,8 @@ Build the project locally, from the project root:
## 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 in case there is any formatting issue. To apply format, you can run the following command:
+Therefore, the previous command (`./gradlew build`) will fail if there are any formatting issues.
+To apply formatting, you can run the following command:
```bash
./gradlew spotlessApply
@@ -23,6 +24,6 @@ Check out some use case at the [Scala examples](../examples/scala) folder.
How to run the examples (from IntelliJ IDEA):
-* Set Java version 19
-* Set VM options: "--enable-preview"
-* Set Env variable: "OPENAI_TOKEN=xxx"
+* Set Java version 20
+* Set VM options: `--enable-preview`
+* Set Env variable: `OPENAI_TOKEN=xxx`
diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/KotlinXSerializers.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/KotlinXSerializers.scala
deleted file mode 100644
index cd57424c2..000000000
--- a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/KotlinXSerializers.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.xebia.functional.xef.scala.conversation
-
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.builtins.BuiltinSerializersKt
-import kotlinx.serialization.builtins.BuiltinSerializersKt.serializer
-
-import java.lang
-
-object KotlinXSerializers:
- val int: KSerializer[Integer] =
- serializer(kotlin.jvm.internal.IntCompanionObject.INSTANCE)
-
- val string: KSerializer[String] =
- serializer(kotlin.jvm.internal.StringCompanionObject.INSTANCE)
-
- val boolean: KSerializer[lang.Boolean] =
- serializer(kotlin.jvm.internal.BooleanCompanionObject.INSTANCE)
-
- val double: KSerializer[lang.Double] =
- serializer(kotlin.jvm.internal.DoubleCompanionObject.INSTANCE)
-
- val float: KSerializer[lang.Float] =
- serializer(kotlin.jvm.internal.FloatCompanionObject.INSTANCE)
-
- val long: KSerializer[lang.Long] =
- serializer(kotlin.jvm.internal.LongCompanionObject.INSTANCE)
-
- val short: KSerializer[lang.Short] =
- serializer(kotlin.jvm.internal.ShortCompanionObject.INSTANCE)
-
- val byte: KSerializer[lang.Byte] =
- serializer(kotlin.jvm.internal.ByteCompanionObject.INSTANCE)
-
- val char: KSerializer[Character] =
- serializer(kotlin.jvm.internal.CharCompanionObject.INSTANCE)
-
- val unit: KSerializer[kotlin.Unit] =
- serializer(kotlin.Unit.INSTANCE)
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
index 7f4d107c6..d65b83efa 100644
--- 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
@@ -1,84 +1,65 @@
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.prompt.Prompt
-import com.xebia.functional.xef.conversation.{FromJson, JVMConversation}
+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.store.{ConversationId, LocalVectorStore, VectorStore}
-import com.xebia.functional.xef.metrics.{LogsMetric, Metric}
+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.{Subscriber, Subscription}
+import org.reactivestreams.*
-import java.util
-import java.util.UUID
+import java.util.UUID.*
import java.util.concurrent.LinkedBlockingQueue
import scala.jdk.CollectionConverters.*
-class ScalaConversation(store: VectorStore, metric: Metric, conversationId: Option[ConversationId])
- extends JVMConversation(store, metric, conversationId.orNull)
+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 = OpenAI.FromEnvironment.DEFAULT_SERIALIZATION
-)(using
- conversation: ScalaConversation
-): A =
- val fromJson = new FromJson[A] {
- def fromJson(json: String): A =
- parse(json).flatMap(Decoder[A].decodeJson(_)).fold(throw _, identity)
- }
+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 = OpenAI.FromEnvironment.DEFAULT_CHAT
-)(using conversation: ScalaConversation): String =
+def promptMessage(prompt: Prompt, chat: Chat = DEFAULT_CHAT)(using conversation: ScalaConversation): String =
conversation.promptMessage(chat, prompt).join()
-def promptMessages(
- prompt: Prompt,
- chat: Chat = OpenAI.FromEnvironment.DEFAULT_CHAT
-)(using
- conversation: ScalaConversation
-): List[String] =
+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 = OpenAI.FromEnvironment.DEFAULT_CHAT
-)(using
- conversation: ScalaConversation
-): LazyList[String] =
+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
+ 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 images(
- prompt: Prompt,
- images: Images = OpenAI.FromEnvironment.DEFAULT_IMAGES,
- numberImages: Int = 1,
- size: String = "1024x1024"
-)(using
+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,
- conversationId: Option[ConversationId] = Some(ConversationId(UUID.randomUUID().toString))
-): A = block(using ScalaConversation(LocalVectorStore(OpenAI.FromEnvironment.DEFAULT_EMBEDDING), LogsMetric(), conversationId))
+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
new file mode 100644
index 000000000..30aa05e4a
--- /dev/null
+++ b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/KotlinXSerializers.scala
@@ -0,0 +1,18 @@
+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/conversation/SerialDescriptor.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala
similarity index 94%
rename from scala/src/main/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptor.scala
rename to scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala
index a7dc121df..9b574065a 100644
--- a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptor.scala
+++ b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptor.scala
@@ -1,20 +1,19 @@
-package com.xebia.functional.xef.scala.conversation
+package com.xebia.functional.xef.scala.serialization
import com.xebia.functional.xef.conversation.jvm.Description as JvmDescription
-import kotlinx.serialization.descriptors.{SerialDescriptor as KtSerialDescriptor, SerialKind, StructureKind}
import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.{SerialDescriptor as KtSerialDescriptor, SerialKind, StructureKind}
import kotlinx.serialization.encoding.{Decoder as KtDecoder, Encoder as KtEncoder}
-import scala.quoted.*
import java.lang.annotation.Annotation
import java.util
-import scala.compiletime.{constValue, erasedValue, summonInline}
+import scala.annotation.meta.*
+import scala.compiletime.*
import scala.deriving.*
import scala.jdk.CollectionConverters.*
+import scala.quoted.*
import scala.reflect.ClassTag
-import scala.annotation.meta._
-
trait SerialDescriptor[A]:
def serialDescriptor: KtSerialDescriptor
def kserializer: KSerializer[A] = new KSerializer[A]:
@@ -81,40 +80,33 @@ object SerialDescriptor extends SerialDescriptorInstances:
// 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 = serialDescriptorImpl
+ def serialDescriptor: KtSerialDescriptor = serialDescriptorImpl
inline def getStaticAnnotations[A]: List[Any] = ${ getAnnotationsImpl[A] }
-def getAnnotationsImpl[A: Type](using Quotes): Expr[List[Any]] = {
+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._
-
+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 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 {
+ 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
diff --git a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorInstances.scala b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala
similarity index 98%
rename from scala/src/main/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorInstances.scala
rename to scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala
index c213c28e9..ee761f27c 100644
--- a/scala/src/main/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorInstances.scala
+++ b/scala/src/main/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorInstances.scala
@@ -1,4 +1,4 @@
-package com.xebia.functional.xef.scala.conversation
+package com.xebia.functional.xef.scala.serialization
import kotlin.jvm.internal.Reflection
import kotlin.reflect.KClass
diff --git a/scala/src/test/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorSpec.scala b/scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala
similarity index 98%
rename from scala/src/test/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorSpec.scala
rename to scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala
index 6141cc617..4c103b6e1 100644
--- a/scala/src/test/scala/com/xebia/functional/xef/scala/conversation/SerialDescriptorSpec.scala
+++ b/scala/src/test/scala/com/xebia/functional/xef/scala/serialization/SerialDescriptorSpec.scala
@@ -1,4 +1,4 @@
-package com.xebia.functional.xef.scala.conversation
+package com.xebia.functional.xef.scala.serialization
import cats.syntax.either.*
import kotlinx.serialization.builtins.BuiltinSerializersKt