diff --git a/.editorconfig b/.editorconfig
index 6fc7e1c..138c31d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -19,7 +19,7 @@ insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
-[pom.xml]
+[*.xml]
indent_size = 4
[*.kt]
@@ -27,3 +27,5 @@ indent_size = 4
max_line_length = 100
ij_kotlin_name_count_to_use_star_import = 999
ij_kotlin_name_count_to_use_star_import_for_members = 999
+# noinspection EditorConfigKeyCorrectness
+ktlint_function_naming_ignore_when_annotated_with = "Test"
diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml
new file mode 100644
index 0000000..315bdfc
--- /dev/null
+++ b/.idea/externalDependencies.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Makefile b/Makefile
index df04911..50873f9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,8 @@
build:
mvn clean verify site
+
+lint:
+ # brew install ktlint
+ ktlint --format
+ # https://docs.openrewrite.org/recipes/maven/bestpractices
+ mvn rewrite:run -P lint
diff --git a/README.md b/README.md
index cf93add..80c9bb9 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Add the following dependencies to your `pom.xml`:
me.kpavlov.langchain4j.kotlin
- langchain4j-core-kotlin
+ langchain4j-kotlin
[LATEST_VERSION]
@@ -47,7 +47,7 @@ Add the following to your `build.gradle.kts`:
```kotlin
dependencies {
- implementation("me.kpavlov.langchain4j.kotlin:langchain4j-core-kotlin:$LATEST_VERSION")
+ implementation("me.kpavlov.langchain4j.kotlin:langchain4j-kotlin:$LATEST_VERSION")
implementation("dev.langchain4j:langchain4j-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm")
}
@@ -113,11 +113,15 @@ Using Make:
make build
```
-
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
+Run before submitting your changes
+```shell
+make lint
+```
+
## Acknowledgements
- [LangChain4j](https://github.com/langchain4j/langchain4j) - The core library this project enhances
diff --git a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt b/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt
deleted file mode 100644
index b6ea8ab..0000000
--- a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-package me.kpavlov.langchain4j.kotlin
-
-import assertk.assertThat
-import assertk.assertions.contains
-import assertk.assertions.isNotNull
-import assertk.assertions.isSuccess
-import dev.langchain4j.data.document.Document
-import dev.langchain4j.data.document.DocumentLoader
-import dev.langchain4j.data.document.parser.TextDocumentParser
-import dev.langchain4j.data.document.source.FileSystemSource
-import dev.langchain4j.data.message.SystemMessage
-import dev.langchain4j.data.message.UserMessage
-import dev.langchain4j.model.chat.ChatLanguageModel
-import dev.langchain4j.model.chat.chatAsync
-import dev.langchain4j.model.chat.generateAsync
-import dev.langchain4j.model.chat.request.ChatRequest
-import dev.langchain4j.model.chat.request.ResponseFormat
-import dev.langchain4j.model.openai.OpenAiChatModel
-import kotlinx.coroutines.test.runTest
-import org.junit.jupiter.api.BeforeAll
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.TestInstance
-import org.slf4j.LoggerFactory
-import java.nio.file.Paths
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-internal class ChatLanguageModelIT {
-
- private val logger = LoggerFactory.getLogger(javaClass)
-
- private val model: ChatLanguageModel = OpenAiChatModel
- .builder()
- .apiKey("demo")
- .modelName("gpt-4o-mini")
- .temperature(0.0)
- .maxTokens(1024)
- .build()
-
- private lateinit var document: Document
-
- @BeforeAll
- fun beforeAll() = runTest {
- document = loadDocument("notes/blumblefang.txt", logger)
- }
-
- @Test
- fun `ChatLanguageModel should generateAsync`() = runTest {
-
- val response = model.generateAsync(
- listOf(
- SystemMessage.from(
- """
- You are helpful advisor answering questions only related to the given text"""
- .trimIndent()
- ),
- UserMessage.from(
- """
- What does Blumblefang love? Text: ```${document.text()}```
- """.trimIndent()
- ),
- )
- )
-
- logger.info("Response: {}", response);
- assertThat(response).isNotNull()
- val content = response.content()
- assertThat(content.text()).contains("Blumblefang loves to help")
- }
-
- @Test
- fun `ChatLanguageModel should chatAsync`() = runTest {
-
- val document = loadDocument("notes/blumblefang.txt", logger)
-
- val response = model.chatAsync(ChatRequest.builder()
- .messages(listOf(
- SystemMessage.from(
- """
- You are helpful advisor answering questions only related to the given text"""
- .trimIndent()
- ),
- UserMessage.from(
- """
- What does Blumblefang love? Text: ```${document.text()}```
- """.trimIndent()
- ),
- ))
- .responseFormat(ResponseFormat.TEXT)
- )
-
- logger.info("Response: {}", response);
- assertThat(response).isNotNull()
- val content = response.aiMessage()
- assertThat(content.text()).contains("Blumblefang loves to help")
- }
-}
diff --git a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt b/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt
deleted file mode 100644
index 3aaa7fc..0000000
--- a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package me.kpavlov.langchain4j.kotlin
-
-import assertk.assertThat
-import assertk.assertions.contains
-import assertk.assertions.isEqualTo
-import assertk.assertions.isNotNull
-import dev.langchain4j.data.message.AiMessage
-import dev.langchain4j.data.message.ChatMessage
-import dev.langchain4j.data.message.SystemMessage
-import dev.langchain4j.data.message.UserMessage
-import dev.langchain4j.model.chat.StreamingChatLanguageModel
-import dev.langchain4j.model.chat.StreamingChatLanguageModelReply.Completion
-import dev.langchain4j.model.chat.StreamingChatLanguageModelReply.Token
-import dev.langchain4j.model.chat.generateFlow
-import dev.langchain4j.model.openai.OpenAiStreamingChatModel
-import dev.langchain4j.model.output.Response
-import kotlinx.coroutines.test.runTest
-import org.junit.jupiter.api.Assertions.fail
-import org.junit.jupiter.api.Disabled
-import org.junit.jupiter.api.Test
-import org.slf4j.LoggerFactory
-import java.util.concurrent.atomic.AtomicReference
-
-@Disabled("Run it manually")
-class StreamingChatLanguageModelIT {
-
- private val logger = LoggerFactory.getLogger(javaClass)
-
- private val model: StreamingChatLanguageModel = OpenAiStreamingChatModel
- .builder()
- .apiKey(TestEnvironment.env("OPENAI_API_KEY"))
- .modelName("gpt-4o-mini")
- .temperature(0.0)
- .maxTokens(100)
- .build()
-
- @Test
- fun `StreamingChatLanguageModel should generateFlow`() = runTest {
- val document = loadDocument("notes/blumblefang.txt", logger)
-
- val messages = listOf(
- SystemMessage.from(
- """
- You are helpful advisor answering questions only related to the given text"""
- .trimIndent()
- ),
- UserMessage.from(
- """
- What does Blumblefang love? Text: ```${document.text()}```
- """.trimIndent()
- ),
- )
-
- val responseRef = AtomicReference?>()
-
- val collectedTokens = mutableListOf()
-
- model.generateFlow(messages)
- .collect {
- logger.info("Received event: $it")
- when (it) {
- is Token -> {
- logger.info("Token: '${it.token}'")
- collectedTokens.add(it.token)
- }
-
- is Completion -> responseRef.set(it.response)
- else -> fail("Unsupported event: $it")
- }
- }
-
- val response = responseRef.get()!!
- assertThat(response.metadata()).isNotNull()
- val content = response.content()
- assertThat(content).isNotNull()
- assertThat(collectedTokens.joinToString(""))
- .isEqualTo(content.text())
- assertThat(content.text()).contains("Blumblefang loves to help")
- }
-}
diff --git a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt b/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt
deleted file mode 100644
index 0c07fed..0000000
--- a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package me.kpavlov.langchain4j.kotlin
-
-import io.github.cdimascio.dotenv.dotenv
-import org.slf4j.LoggerFactory
-import java.nio.file.Paths
-
-private val logger = LoggerFactory.getLogger(TestEnvironment.javaClass)
-
-object TestEnvironment {
- private val dotenv =
- dotenv {
- directory = Paths.get("${System.getProperty("user.dir")}/..").normalize().toString()
- logger.info("Loading .env file from $directory")
- ignoreIfMissing = true
- }
-
- fun env(name: String) = dotenv.get(name)
-}
diff --git a/langchain4j-kotlin/notebooks/lc4kNotebook.ipynb b/langchain4j-kotlin/notebooks/lc4kNotebook.ipynb
new file mode 100644
index 0000000..d1187ec
--- /dev/null
+++ b/langchain4j-kotlin/notebooks/lc4kNotebook.ipynb
@@ -0,0 +1,94 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "# Welcome to LangChain4j-Kotlin Notebook!"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-11-13T08:41:13.973072Z",
+ "start_time": "2024-11-13T08:41:11.937475Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "import dev.langchain4j.model.chat.generateAsync\n",
+ "import dev.langchain4j.model.openai.OpenAiChatModel\n",
+ "import dev.langchain4j.data.message.SystemMessage\n",
+ "import dev.langchain4j.data.message.UserMessage\n",
+ "import kotlinx.coroutines.runBlocking\n",
+ "\n",
+ "val model = OpenAiChatModel\n",
+ " .builder()\n",
+ " .apiKey(\"demo\")\n",
+ " .modelName(\"gpt-4o-mini\")\n",
+ " .temperature(0.0)\n",
+ " .maxTokens(1024)\n",
+ " .build()\n",
+ "\n",
+ "runBlocking {\n",
+ " val result = model.generateAsync(\n",
+ " listOf(\n",
+ " SystemMessage.from(\"You are helpful assistant\"),\n",
+ " UserMessage.from(\"Make a joke about Kotlin, Langchani4j and LLM\"),\n",
+ " )\n",
+ " )\n",
+ " \n",
+ " println(result.content().text())\n",
+ "}\n",
+ "\n",
+ " \n",
+ " "
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Why did the Kotlin developer break up with Langchain4j?\n",
+ "\n",
+ "Because they found a new love in LLM that could handle their \"null\" feelings without throwing any exceptions!\n"
+ ]
+ }
+ ],
+ "execution_count": 36
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2024-11-13T08:39:55.837900Z",
+ "start_time": "2024-11-13T08:39:55.836524Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "",
+ "outputs": [],
+ "execution_count": null
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Kotlin",
+ "language": "kotlin",
+ "name": "kotlin"
+ },
+ "language_info": {
+ "name": "kotlin",
+ "version": "1.9.23",
+ "mimetype": "text/x-kotlin",
+ "file_extension": ".kt",
+ "pygments_lexer": "kotlin",
+ "codemirror_mode": "text/x-kotlin",
+ "nbconvert_exporter": ""
+ },
+ "ktnbPluginMetadata": {
+ "projectDependencies": [
+ "langchain4j-kotlin"
+ ]
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/langchain4j-core-kotlin/pom.xml b/langchain4j-kotlin/pom.xml
similarity index 65%
rename from langchain4j-core-kotlin/pom.xml
rename to langchain4j-kotlin/pom.xml
index 4e54ed9..245a69b 100644
--- a/langchain4j-core-kotlin/pom.xml
+++ b/langchain4j-kotlin/pom.xml
@@ -1,13 +1,15 @@
-
+
4.0.0
me.kpavlov.langchain4j.kotlin
- langchain4j-kotlin-aggregator
+ root
0.1.1-SNAPSHOT
+ ../pom.xml
- langchain4j-core-kotlin
+ langchain4j-kotlin
LangChain4j-Kotlin :: Core
@@ -22,9 +24,13 @@
org.slf4j
slf4j-api
- ${slf4j.version}
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
dev.langchain4j
langchain4j
@@ -35,6 +41,11 @@
langchain4j-open-ai
test
+
+ me.kpavlov.finchly
+ finchly
+ test
+
diff --git a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/internal/Logging.kt b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/internal/Logging.kt
similarity index 63%
rename from langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/internal/Logging.kt
rename to langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/internal/Logging.kt
index c404544..19d9354 100644
--- a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/internal/Logging.kt
+++ b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/internal/Logging.kt
@@ -1,4 +1,4 @@
-package dev.langchain4j.internal
+package me.kpavlov.langchain4j.kotlin.internal
import org.slf4j.MarkerFactory
diff --git a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/ChatLanguageModelExtensions.kt b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/ChatLanguageModelExtensions.kt
similarity index 94%
rename from langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/ChatLanguageModelExtensions.kt
rename to langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/ChatLanguageModelExtensions.kt
index 9b0747d..b2b7ec4 100644
--- a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/ChatLanguageModelExtensions.kt
+++ b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/ChatLanguageModelExtensions.kt
@@ -1,7 +1,8 @@
-package dev.langchain4j.model.chat
+package me.kpavlov.langchain4j.kotlin.model.chat
import dev.langchain4j.data.message.AiMessage
import dev.langchain4j.data.message.ChatMessage
+import dev.langchain4j.model.chat.ChatLanguageModel
import dev.langchain4j.model.chat.request.ChatRequest
import dev.langchain4j.model.chat.response.ChatResponse
import dev.langchain4j.model.output.Response
@@ -55,9 +56,8 @@ suspend fun ChatLanguageModel.chatAsync(request: ChatRequest): ChatResponse {
* @see ChatRequest.Builder
* @see chatAsync
*/
-suspend fun ChatLanguageModel.chatAsync(requestBuilder: ChatRequest.Builder): ChatResponse {
- return chatAsync(requestBuilder.build())
-}
+suspend fun ChatLanguageModel.chatAsync(requestBuilder: ChatRequest.Builder): ChatResponse =
+ chatAsync(requestBuilder.build())
/**
* Processes a chat request using a [ChatRequest.Builder] for convenient request
@@ -82,9 +82,8 @@ suspend fun ChatLanguageModel.chatAsync(requestBuilder: ChatRequest.Builder): Ch
* @see ChatResponse
* @see ChatRequest.Builder
*/
-fun ChatLanguageModel.chat(requestBuilder: ChatRequest.Builder): ChatResponse {
- return this.chat(requestBuilder.build())
-}
+fun ChatLanguageModel.chat(requestBuilder: ChatRequest.Builder): ChatResponse =
+ this.chat(requestBuilder.build())
/**
* Asynchronously generates a response for a list of chat messages using
@@ -113,5 +112,3 @@ suspend fun ChatLanguageModel.generateAsync(messages: List): Respon
val model = this
return coroutineScope { model.generate(messages) }
}
-
-
diff --git a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/StreamingChatLanguageModelExtensions.kt b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/StreamingChatLanguageModelExtensions.kt
similarity index 61%
rename from langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/StreamingChatLanguageModelExtensions.kt
rename to langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/StreamingChatLanguageModelExtensions.kt
index e8c6bc9..93e60c9 100644
--- a/langchain4j-core-kotlin/src/main/kotlin/dev/langchain4j/model/chat/StreamingChatLanguageModelExtensions.kt
+++ b/langchain4j-kotlin/src/main/kotlin/me/kpavlov/langchain4j/kotlin/model/chat/StreamingChatLanguageModelExtensions.kt
@@ -1,13 +1,14 @@
-package dev.langchain4j.model.chat
+package me.kpavlov.langchain4j.kotlin.model.chat
import dev.langchain4j.data.message.AiMessage
import dev.langchain4j.data.message.ChatMessage
-import dev.langchain4j.internal.PII
import dev.langchain4j.model.StreamingResponseHandler
+import dev.langchain4j.model.chat.StreamingChatLanguageModel
import dev.langchain4j.model.output.Response
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import me.kpavlov.langchain4j.kotlin.internal.PII
import org.slf4j.LoggerFactory
private val logger = LoggerFactory.getLogger(StreamingChatLanguageModel::class.java)
@@ -22,14 +23,19 @@ sealed interface StreamingChatLanguageModelReply {
*
* @property token The individual token string generated by the model.
*/
- data class Token(val token: String) : StreamingChatLanguageModelReply
+ data class Token(
+ val token: String,
+ ) : StreamingChatLanguageModelReply
+
/**
* Represents the complete response received at the end of the streaming process.
* This includes the final message along with any additional metadata.
*
* @property response The complete response containing the AI message and associated metadata.
*/
- data class Completion(val response: Response) : StreamingChatLanguageModelReply
+ data class Completion(
+ val response: Response,
+ ) : StreamingChatLanguageModelReply
}
/**
@@ -64,37 +70,42 @@ sealed interface StreamingChatLanguageModelReply {
*/
fun StreamingChatLanguageModel.generateFlow(
messages: List,
-): Flow = callbackFlow {
-
- val model = this@generateFlow
+): Flow =
+ callbackFlow {
+ val model = this@generateFlow
- val handler = object : StreamingResponseHandler {
- override fun onNext(token: String) {
- logger.trace(PII, "Received token: {}", token)
- trySend(StreamingChatLanguageModelReply.Token(token))
- }
+ val handler =
+ object : StreamingResponseHandler {
+ override fun onNext(token: String) {
+ logger.trace(
+ me.kpavlov.langchain4j.kotlin.internal.PII,
+ "Received token: {}",
+ token,
+ )
+ trySend(StreamingChatLanguageModelReply.Token(token))
+ }
- override fun onComplete(response: Response) {
- logger.trace(PII, "Received response: {}", response)
- trySend(StreamingChatLanguageModelReply.Completion(response))
- close()
- }
+ override fun onComplete(response: Response) {
+ logger.trace(
+ me.kpavlov.langchain4j.kotlin.internal.PII,
+ "Received response: {}",
+ response,
+ )
+ trySend(StreamingChatLanguageModelReply.Completion(response))
+ close()
+ }
- override fun onError(error: Throwable) {
- close(error)
- }
- }
+ override fun onError(error: Throwable) {
+ close(error)
+ }
+ }
- logger.info("Starting flow...")
- model.generate(messages, handler)
+ logger.info("Starting flow...")
+ model.generate(messages, handler)
- // This will be called when the flow collection is closed or cancelled.
- awaitClose {
- // cleanup
- logger.info("Flow is canceled")
+ // This will be called when the flow collection is closed or cancelled.
+ awaitClose {
+ // cleanup
+ logger.info("Flow is canceled")
+ }
}
-}
-
-
-
-
diff --git a/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt
new file mode 100644
index 0000000..1a55ef8
--- /dev/null
+++ b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/ChatLanguageModelIT.kt
@@ -0,0 +1,99 @@
+package me.kpavlov.langchain4j.kotlin
+
+import assertk.assertThat
+import assertk.assertions.contains
+import assertk.assertions.isNotNull
+import dev.langchain4j.data.document.Document
+import dev.langchain4j.data.message.SystemMessage
+import dev.langchain4j.data.message.UserMessage
+import dev.langchain4j.model.chat.ChatLanguageModel
+import dev.langchain4j.model.chat.request.ChatRequest
+import dev.langchain4j.model.chat.request.ResponseFormat
+import dev.langchain4j.model.openai.OpenAiChatModel
+import kotlinx.coroutines.test.runTest
+import me.kpavlov.langchain4j.kotlin.model.chat.chatAsync
+import me.kpavlov.langchain4j.kotlin.model.chat.generateAsync
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.slf4j.LoggerFactory
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class ChatLanguageModelIT {
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ private val model: ChatLanguageModel =
+ OpenAiChatModel
+ .builder()
+ .apiKey(TestEnvironment.openaiApiKey)
+ .modelName("gpt-4o-mini")
+ .temperature(0.0)
+ .maxTokens(1024)
+ .build()
+
+ private lateinit var document: Document
+
+ @BeforeAll
+ fun beforeAll() =
+ runTest {
+ document = loadDocument("notes/blumblefang.txt", logger)
+ }
+
+ @Test
+ fun `ChatLanguageModel should generateAsync`() =
+ runTest {
+ val response =
+ model.generateAsync(
+ listOf(
+ SystemMessage.from(
+ """
+ You are helpful advisor answering questions only related to the given text
+
+ """.trimIndent(),
+ ),
+ UserMessage.from(
+ """
+ What does Blumblefang love? Text: ```${document.text()}```
+ """.trimIndent(),
+ ),
+ ),
+ )
+
+ logger.info("Response: {}", response)
+ assertThat(response).isNotNull()
+ val content = response.content()
+ assertThat(content.text()).contains("Blumblefang loves to help")
+ }
+
+ @Test
+ fun `ChatLanguageModel should chatAsync`() =
+ runTest {
+ val document = loadDocument("notes/blumblefang.txt", logger)
+
+ val response =
+ model.chatAsync(
+ ChatRequest
+ .builder()
+ .messages(
+ listOf(
+ SystemMessage.from(
+ """
+ You are helpful advisor answering questions only related to the given text
+
+ """.trimIndent(),
+ ),
+ UserMessage.from(
+ """
+ What does Blumblefang love? Text: ```${document.text()}```
+ """.trimIndent(),
+ ),
+ ),
+ ).responseFormat(ResponseFormat.TEXT),
+ )
+
+ logger.info("Response: {}", response)
+ assertThat(response).isNotNull()
+ val content = response.aiMessage()
+ assertThat(content.text()).contains("Blumblefang loves to help")
+ }
+}
diff --git a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt
similarity index 87%
rename from langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt
rename to langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt
index 9853f1b..c2818ae 100644
--- a/langchain4j-core-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt
+++ b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/Documents.kt
@@ -7,7 +7,10 @@ import dev.langchain4j.data.document.source.FileSystemSource
import org.slf4j.Logger
import java.nio.file.Paths
-suspend fun loadDocument(documentName: String,logger: Logger) : Document {
+suspend fun loadDocument(
+ documentName: String,
+ logger: Logger,
+): Document {
val source = FileSystemSource(Paths.get("./src/test/resources/data/$documentName"))
val document = DocumentLoader.load(source, TextDocumentParser())
diff --git a/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt
new file mode 100644
index 0000000..cfa6461
--- /dev/null
+++ b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/StreamingChatLanguageModelIT.kt
@@ -0,0 +1,87 @@
+package me.kpavlov.langchain4j.kotlin
+
+import assertk.assertThat
+import assertk.assertions.contains
+import assertk.assertions.isEqualTo
+import assertk.assertions.isNotNull
+import dev.langchain4j.data.message.AiMessage
+import dev.langchain4j.data.message.ChatMessage
+import dev.langchain4j.data.message.SystemMessage
+import dev.langchain4j.data.message.UserMessage
+import dev.langchain4j.model.chat.StreamingChatLanguageModel
+import dev.langchain4j.model.openai.OpenAiStreamingChatModel
+import dev.langchain4j.model.output.Response
+import kotlinx.coroutines.test.runTest
+import me.kpavlov.langchain4j.kotlin.model.chat.StreamingChatLanguageModelReply.Completion
+import me.kpavlov.langchain4j.kotlin.model.chat.StreamingChatLanguageModelReply.Token
+import me.kpavlov.langchain4j.kotlin.model.chat.generateFlow
+import org.junit.jupiter.api.Assertions.fail
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable
+import org.slf4j.LoggerFactory
+import java.util.concurrent.atomic.AtomicReference
+
+@EnabledIfEnvironmentVariable(
+ named = "OPENAI_API_KEY",
+ matches = ".+",
+)
+class StreamingChatLanguageModelIT {
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ private val model: StreamingChatLanguageModel =
+ OpenAiStreamingChatModel
+ .builder()
+ .apiKey(TestEnvironment.openaiApiKey)
+ .modelName("gpt-4o-mini")
+ .temperature(0.0)
+ .maxTokens(100)
+ .build()
+
+ @Test
+ fun `StreamingChatLanguageModel should generateFlow`() =
+ runTest {
+ val document = loadDocument("notes/blumblefang.txt", logger)
+
+ val messages =
+ listOf(
+ SystemMessage.from(
+ """
+ You are helpful advisor answering questions only related to the given text
+
+ """.trimIndent(),
+ ),
+ UserMessage.from(
+ """
+ What does Blumblefang love? Text: ```${document.text()}```
+ """.trimIndent(),
+ ),
+ )
+
+ val responseRef = AtomicReference?>()
+
+ val collectedTokens = mutableListOf()
+
+ model
+ .generateFlow(messages)
+ .collect {
+ logger.info("Received event: $it")
+ when (it) {
+ is Token -> {
+ logger.info("Token: '${it.token}'")
+ collectedTokens.add(it.token)
+ }
+
+ is Completion -> responseRef.set(it.response)
+ else -> fail("Unsupported event: $it")
+ }
+ }
+
+ val response = responseRef.get()!!
+ assertThat(response.metadata()).isNotNull()
+ val content = response.content()
+ assertThat(content).isNotNull()
+ assertThat(collectedTokens.joinToString(""))
+ .isEqualTo(content.text())
+ assertThat(content.text()).contains("Blumblefang loves to help")
+ }
+}
diff --git a/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt
new file mode 100644
index 0000000..8e65690
--- /dev/null
+++ b/langchain4j-kotlin/src/test/kotlin/me/kpavlov/langchain4j/kotlin/TestEnvironment.kt
@@ -0,0 +1,7 @@
+package me.kpavlov.langchain4j.kotlin
+
+object TestEnvironment : me.kpavlov.finchly.BaseTestEnvironment(
+ dotEnvFileDir = "../",
+) {
+ val openaiApiKey = TestEnvironment.get("OPENAI_API_KEY", "demo")
+}
diff --git a/langchain4j-core-kotlin/src/test/resources/data/books/captain-blood.txt b/langchain4j-kotlin/src/test/resources/data/books/captain-blood.txt
similarity index 100%
rename from langchain4j-core-kotlin/src/test/resources/data/books/captain-blood.txt
rename to langchain4j-kotlin/src/test/resources/data/books/captain-blood.txt
diff --git a/langchain4j-core-kotlin/src/test/resources/data/notes/blumblefang.txt b/langchain4j-kotlin/src/test/resources/data/notes/blumblefang.txt
similarity index 100%
rename from langchain4j-core-kotlin/src/test/resources/data/notes/blumblefang.txt
rename to langchain4j-kotlin/src/test/resources/data/notes/blumblefang.txt
diff --git a/langchain4j-core-kotlin/src/test/resources/data/notes/quantum-computing.txt b/langchain4j-kotlin/src/test/resources/data/notes/quantum-computing.txt
similarity index 100%
rename from langchain4j-core-kotlin/src/test/resources/data/notes/quantum-computing.txt
rename to langchain4j-kotlin/src/test/resources/data/notes/quantum-computing.txt
diff --git a/pom.xml b/pom.xml
index f7eeeae..c77520e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,20 +1,46 @@
-
+
4.0.0
me.kpavlov.langchain4j.kotlin
- langchain4j-kotlin-aggregator
+ root
0.1.1-SNAPSHOT
pom
LangChain4j-Kotlin :: Aggregator
Kotlin enhancements for LangChain4j
https://github.com/kpavlov/langchain4j-kotlin
+
+
+ MIT License
+ https://opensource.org/license/mit
+
+
+
+
+
+ Konstantin Pavlov
+ https://kpavlov.me
+
+ author
+ owner
+
+
+
+
- langchain4j-core-kotlin
+ langchain4j-kotlin
reports
+
+ scm:git:git://github.com/kpavlov/langchain4j-kotlin.git
+ scm:git:ssh://github.com/kpavlov/langchain4j-kotlin.git
+ https://github.com/kpavlov/langchain4j-kotlin/tree/main
+ HEAD
+
+
UTF-8
official
@@ -24,7 +50,7 @@
${java.version}
4.2.2
- 6.4.2
+ 0.1.1
5.11.3
1.9.0
0.35.0
@@ -65,6 +91,13 @@
pom
import
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
org.slf4j
slf4j-simple
@@ -77,6 +110,12 @@
0.28.1
test
+
+ me.kpavlov.finchly
+ finchly
+ ${finchly.version}
+ test
+
@@ -93,24 +132,6 @@
${awaitility.version}
test
-
- io.github.cdimascio
- dotenv-kotlin
- ${dotenv-kotlin.version}
- test
-
-
- org.jetbrains.kotlin
- kotlin-test-junit5
- test
-
-
- org.junit.jupiter
- junit-jupiter
- ${junit.version}
- test
-
-
org.jetbrains.kotlinx
kotlinx-coroutines-test
@@ -134,10 +155,12 @@
+ org.apache.maven.plugins
maven-surefire-plugin
3.5.2
+ org.apache.maven.plugins
maven-failsafe-plugin
3.5.2
@@ -182,6 +205,11 @@
maven-javadoc-plugin
3.11.1
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 3.6.2
+
@@ -212,6 +240,7 @@
+ org.apache.maven.plugins
maven-failsafe-plugin
@@ -284,31 +313,6 @@
-
- scm:git:git://github.com/kpavlov/langchain4j-kotlin.git
- scm:git:ssh://github.com/kpavlov/langchain4j-kotlin.git
- https://github.com/kpavlov/langchain4j-kotlin/tree/main
- HEAD
-
-
-
-
- MIT License
- https://opensource.org/license/mit
-
-
-
-
-
- Konstantin Pavlov
- https://kpavlov.me
-
- author
- owner
-
-
-
-
release
@@ -355,6 +359,25 @@
+
+ lint
+
+
+
+ org.openrewrite.maven
+ rewrite-maven-plugin
+ 5.43.0
+
+ true
+
+ org.openrewrite.maven.BestPractices
+
+ true
+
+
+
+
+
diff --git a/reports/pom.xml b/reports/pom.xml
index bf93b6d..a511211 100644
--- a/reports/pom.xml
+++ b/reports/pom.xml
@@ -3,8 +3,9 @@
4.0.0
me.kpavlov.langchain4j.kotlin
- langchain4j-kotlin-aggregator
+ root
0.1.1-SNAPSHOT
+ ../pom.xml
reports
@@ -14,7 +15,7 @@
me.kpavlov.langchain4j.kotlin
- langchain4j-core-kotlin
+ langchain4j-kotlin
${project.parent.version}