From d27e23382e475c6b5efafc6a6ccbce746bbc8ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Mon, 7 Aug 2023 15:08:49 +0200 Subject: [PATCH 1/8] add new module for the server --- gradle/libs.versions.toml | 2 ++ server/build.gradle.kts | 34 +++++++++++++++++++ .../com/xebia/functional/xef/server/Main.kt | 8 +++++ settings.gradle.kts | 4 +++ 4 files changed, 48 insertions(+) create mode 100644 server/build.gradle.kts create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2ebfb0e1c..0f489d624 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,6 +56,8 @@ ktor-client-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json" ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } +ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" } okio-nodefilesystem = { module = "com.squareup.okio:okio-nodefilesystem", version.ref = "okio" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 000000000..0adc5993a --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id(libs.plugins.kotlin.jvm.get().pluginId) + id(libs.plugins.kotlinx.serialization.get().pluginId) +} + +repositories { + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +dependencies { + implementation(projects.xefCore) + implementation(projects.xefKotlin) + implementation(libs.kotlinx.serialization.json) + implementation(libs.logback) + implementation(libs.klogging) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.core) +} + +tasks.getByName("processResources") { + dependsOn(projects.xefGpt4all.dependencyProject.tasks.getByName("jvmProcessResources")) + from("${projects.xefGpt4all.dependencyProject.buildDir}/processedResources/jvm/main") + into("$buildDir/resources/main") +} + + diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt new file mode 100644 index 000000000..b7c24b0b3 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt @@ -0,0 +1,8 @@ +package com.xebia.functional.xef.server + +object Main { + @JvmStatic + fun main(args: Array) { + println("Hello, world!") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 08ff7e6c0..c59fdb9c0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -87,3 +87,7 @@ 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") From ee2e55b85e0b16d2c58778449486c66d021fcbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Mon, 7 Aug 2023 17:31:20 +0200 Subject: [PATCH 2/8] added basic endpoint --- gradle/libs.versions.toml | 9 +++++ server/build.gradle.kts | 7 ++++ .../com/xebia/functional/xef/server/Main.kt | 29 ++++++++++++++- .../xef/server/http/routes/Requests.kt | 10 +++++ .../xef/server/http/routes/Routes.kt | 37 +++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0f489d624..b07924e7d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,10 +40,14 @@ jackson = "2.15.2" jsonschema = "4.31.1" jakarta = "3.0.2" suspend-transform = "0.3.1" +suspendApp = "0.4.0" [libraries] arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } +arrow-continuations = { module = "io.arrow-kt:arrow-continuations", version.ref = "arrow" } arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } +suspendApp-core = { module = "io.arrow-kt:suspendapp", version.ref = "suspendApp" } +suspendApp-ktor = { module = "io.arrow-kt:suspendapp-ktor", version.ref = "suspendApp" } open-ai = { module = "com.theokanning.openai-gpt3-java:service", version.ref = "openai" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-json" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref="kotlinx-coroutines" } @@ -58,6 +62,10 @@ ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor-server-resources = { module = "io.ktor:ktor-server-resources", version.ref = "ktor" } +ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" } +ktor-server-request-validation = { module = "io.ktor:ktor-server-request-validation", version.ref = "ktor" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" } okio-nodefilesystem = { module = "com.squareup.okio:okio-nodefilesystem", version.ref = "okio" } @@ -67,6 +75,7 @@ kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } kotest-testcontainers = { module = "io.kotest.extensions:kotest-extensions-testcontainers", version.ref = "kotest-testcontainers" } kotest-assertions-arrow = { module = "io.kotest.extensions:kotest-assertions-arrow", version.ref = "kotest-arrow" } +ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } uuid = { module = "app.softwork:kotlinx-uuid-core", version.ref = "uuid" } klogging = { module = "io.github.oshai:kotlin-logging", version.ref = "klogging" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 0adc5993a..ecf81b185 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -23,6 +23,13 @@ dependencies { implementation(libs.klogging) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.core) + implementation(libs.ktor.server.contentNegotiation) + implementation(libs.ktor.server.resources) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.serialization.json) + implementation(libs.suspendApp.core) + implementation(libs.suspendApp.ktor) + implementation(libs.ktor.server.request.validation) } tasks.getByName("processResources") { diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt index b7c24b0b3..5cd8f10d5 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt @@ -1,8 +1,33 @@ package com.xebia.functional.xef.server + +import arrow.continuations.SuspendApp +import arrow.fx.coroutines.resourceScope +import arrow.continuations.ktor.server +import com.xebia.functional.xef.server.http.routes.routes +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.cors.routing.* +import io.ktor.server.resources.* +import io.ktor.server.routing.* +import kotlinx.coroutines.awaitCancellation + object Main { @JvmStatic - fun main(args: Array) { - println("Hello, world!") + fun main(args: Array) = SuspendApp { + resourceScope { + server(factory = Netty, port = 8080, host = "0.0.0.0") { + install(CORS) { + allowNonSimpleContentTypes = true + anyHost() + } + install(ContentNegotiation) { json() } + install(Resources) + routing { routes() } + } + awaitCancellation() + } } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt new file mode 100644 index 000000000..910067887 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt @@ -0,0 +1,10 @@ +package com.xebia.functional.xef.server.http.routes + +import io.ktor.resources.* +import kotlinx.serialization.Serializable + +@Serializable +@Resource("/prompt/message") +data class PromptMessageRequest( + val message: String +) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt new file mode 100644 index 000000000..1a2c92bb1 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -0,0 +1,37 @@ +package com.xebia.functional.xef.server.http.routes + +import com.xebia.functional.xef.auto.ai +import com.xebia.functional.xef.auto.llm.openai.getOrThrow +import com.xebia.functional.xef.auto.llm.openai.promptMessage +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.pipeline.* + +fun Routing.routes() { + post("/prompt/message") { + val provider = call.request.headers["provider"]?.let { + it + } ?: "OpenAI" + println("provider: $provider") + val data = call.receive() + response { + ai { + promptMessage(data.message) + }.getOrThrow() + } + } +} + +/** + * Responds with the data and converts any potential Throwable into a 404. + */ +private suspend inline fun PipelineContext<*, ApplicationCall>.response( + block: () -> T +) = arrow.core.raise.recover({ + call.respond(block()) +}) { + call.respondText(it.message ?: "Response not found", status = HttpStatusCode.NotFound) +} From 9a9ffbbf1551febe9e424d03f49332b04735b073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Mon, 7 Aug 2023 15:08:49 +0200 Subject: [PATCH 3/8] add new module for the server --- gradle/libs.versions.toml | 2 ++ server/build.gradle.kts | 34 +++++++++++++++++++ .../com/xebia/functional/xef/server/Main.kt | 8 +++++ settings.gradle.kts | 4 +++ 4 files changed, 48 insertions(+) create mode 100644 server/build.gradle.kts create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c5cff0a41..8784e0392 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,8 @@ ktor-client-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json" ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } +ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" } okio-nodefilesystem = { module = "com.squareup.okio:okio-nodefilesystem", version.ref = "okio" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 000000000..0adc5993a --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id(libs.plugins.kotlin.jvm.get().pluginId) + id(libs.plugins.kotlinx.serialization.get().pluginId) +} + +repositories { + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +dependencies { + implementation(projects.xefCore) + implementation(projects.xefKotlin) + implementation(libs.kotlinx.serialization.json) + implementation(libs.logback) + implementation(libs.klogging) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.core) +} + +tasks.getByName("processResources") { + dependsOn(projects.xefGpt4all.dependencyProject.tasks.getByName("jvmProcessResources")) + from("${projects.xefGpt4all.dependencyProject.buildDir}/processedResources/jvm/main") + into("$buildDir/resources/main") +} + + diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt new file mode 100644 index 000000000..b7c24b0b3 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt @@ -0,0 +1,8 @@ +package com.xebia.functional.xef.server + +object Main { + @JvmStatic + fun main(args: Array) { + println("Hello, world!") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 08ff7e6c0..c59fdb9c0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -87,3 +87,7 @@ 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") From b1fa32bf384b36245284b98cbc2d969ac81762bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Mon, 7 Aug 2023 17:31:20 +0200 Subject: [PATCH 4/8] added basic endpoint --- gradle/libs.versions.toml | 9 +++++ server/build.gradle.kts | 7 ++++ .../com/xebia/functional/xef/server/Main.kt | 29 ++++++++++++++- .../xef/server/http/routes/Requests.kt | 10 +++++ .../xef/server/http/routes/Routes.kt | 37 +++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8784e0392..d7d4b84aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,10 +39,14 @@ jackson = "2.15.2" jsonschema = "4.31.1" jakarta = "3.0.2" suspend-transform = "0.3.1" +suspendApp = "0.4.0" [libraries] arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } +arrow-continuations = { module = "io.arrow-kt:arrow-continuations", version.ref = "arrow" } arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } +suspendApp-core = { module = "io.arrow-kt:suspendapp", version.ref = "suspendApp" } +suspendApp-ktor = { module = "io.arrow-kt:suspendapp-ktor", version.ref = "suspendApp" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-json" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref="kotlinx-coroutines" } kotlinx-coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref="kotlinx-coroutines-reactive" } @@ -56,6 +60,10 @@ ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor-server-resources = { module = "io.ktor:ktor-server-resources", version.ref = "ktor" } +ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" } +ktor-server-request-validation = { module = "io.ktor:ktor-server-request-validation", version.ref = "ktor" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" } okio-nodefilesystem = { module = "com.squareup.okio:okio-nodefilesystem", version.ref = "okio" } @@ -65,6 +73,7 @@ kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } kotest-testcontainers = { module = "io.kotest.extensions:kotest-extensions-testcontainers", version.ref = "kotest-testcontainers" } kotest-assertions-arrow = { module = "io.kotest.extensions:kotest-assertions-arrow", version.ref = "kotest-arrow" } +ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } uuid = { module = "app.softwork:kotlinx-uuid-core", version.ref = "uuid" } klogging = { module = "io.github.oshai:kotlin-logging", version.ref = "klogging" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 0adc5993a..ecf81b185 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -23,6 +23,13 @@ dependencies { implementation(libs.klogging) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.core) + implementation(libs.ktor.server.contentNegotiation) + implementation(libs.ktor.server.resources) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.serialization.json) + implementation(libs.suspendApp.core) + implementation(libs.suspendApp.ktor) + implementation(libs.ktor.server.request.validation) } tasks.getByName("processResources") { diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt index b7c24b0b3..5cd8f10d5 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt @@ -1,8 +1,33 @@ package com.xebia.functional.xef.server + +import arrow.continuations.SuspendApp +import arrow.fx.coroutines.resourceScope +import arrow.continuations.ktor.server +import com.xebia.functional.xef.server.http.routes.routes +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.cors.routing.* +import io.ktor.server.resources.* +import io.ktor.server.routing.* +import kotlinx.coroutines.awaitCancellation + object Main { @JvmStatic - fun main(args: Array) { - println("Hello, world!") + fun main(args: Array) = SuspendApp { + resourceScope { + server(factory = Netty, port = 8080, host = "0.0.0.0") { + install(CORS) { + allowNonSimpleContentTypes = true + anyHost() + } + install(ContentNegotiation) { json() } + install(Resources) + routing { routes() } + } + awaitCancellation() + } } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt new file mode 100644 index 000000000..910067887 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt @@ -0,0 +1,10 @@ +package com.xebia.functional.xef.server.http.routes + +import io.ktor.resources.* +import kotlinx.serialization.Serializable + +@Serializable +@Resource("/prompt/message") +data class PromptMessageRequest( + val message: String +) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt new file mode 100644 index 000000000..1a2c92bb1 --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -0,0 +1,37 @@ +package com.xebia.functional.xef.server.http.routes + +import com.xebia.functional.xef.auto.ai +import com.xebia.functional.xef.auto.llm.openai.getOrThrow +import com.xebia.functional.xef.auto.llm.openai.promptMessage +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.pipeline.* + +fun Routing.routes() { + post("/prompt/message") { + val provider = call.request.headers["provider"]?.let { + it + } ?: "OpenAI" + println("provider: $provider") + val data = call.receive() + response { + ai { + promptMessage(data.message) + }.getOrThrow() + } + } +} + +/** + * Responds with the data and converts any potential Throwable into a 404. + */ +private suspend inline fun PipelineContext<*, ApplicationCall>.response( + block: () -> T +) = arrow.core.raise.recover({ + call.respond(block()) +}) { + call.respondText(it.message ?: "Response not found", status = HttpStatusCode.NotFound) +} From ae92137dbd00aa95a6a0f9cec6c899d5c62b205a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Tue, 8 Aug 2023 15:42:27 +0200 Subject: [PATCH 5/8] added integration with open AI call --- .../functional/xef/auto/llm/openai/OpenAI.kt | 23 +++++++ server/build.gradle.kts | 1 + .../xef/server/http/routes/Requests.kt | 32 ++++++++- .../xef/server/http/routes/Routes.kt | 66 +++++++++++++++---- 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/openai/src/commonMain/kotlin/com/xebia/functional/xef/auto/llm/openai/OpenAI.kt b/openai/src/commonMain/kotlin/com/xebia/functional/xef/auto/llm/openai/OpenAI.kt index 0c1f1c10c..de58aa4d3 100644 --- a/openai/src/commonMain/kotlin/com/xebia/functional/xef/auto/llm/openai/OpenAI.kt +++ b/openai/src/commonMain/kotlin/com/xebia/functional/xef/auto/llm/openai/OpenAI.kt @@ -72,4 +72,27 @@ class OpenAI(internal val token: String) : AutoCloseable, AutoClose by autoClose @JvmField val DEFAULT_IMAGES = DEFAULT.DALLE_2 } + + fun supportedModels(): List { + return listOf( + GPT_4, + GPT_4_0314, + GPT_4_32K, + GPT_3_5_TURBO, + GPT_3_5_TURBO_16K, + GPT_3_5_TURBO_FUNCTIONS, + GPT_3_5_TURBO_0301, + TEXT_DAVINCI_003, + TEXT_DAVINCI_002, + TEXT_CURIE_001, + TEXT_BABBAGE_001, + TEXT_ADA_001, + TEXT_EMBEDDING_ADA_002, + DALLE_2 + ) + } +} + +fun String.toOpenAIModel(): OpenAIModel? { + return OpenAI.DEFAULT.supportedModels().find { it.name == this } } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index ecf81b185..3876217a1 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(libs.suspendApp.core) implementation(libs.suspendApp.ktor) implementation(libs.ktor.server.request.validation) + implementation(libs.openai.client) } tasks.getByName("processResources") { diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt index 910067887..370e305fe 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt @@ -1,10 +1,38 @@ package com.xebia.functional.xef.server.http.routes import io.ktor.resources.* +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -@Resource("/prompt/message") +data class ChatMessage( + val role: ChatRole, val content: String? = null, val name: String? = null +) + +@JvmInline +@Serializable +value class ChatRole(val role: String) { + public companion object { + public val System: ChatRole = ChatRole("system") + public val User: ChatRole = ChatRole("user") + public val Assistant: ChatRole = ChatRole("assistant") + public val Function: ChatRole = ChatRole("function") + } +} + + +@Serializable +@Resource("/chat/completions") data class PromptMessageRequest( - val message: String + val model: String, + val messages: List, + val temperature: Double = 1.0, + @SerialName("top_p") val topP: Double = 1.0, + val n: Int = 1, + val stop: List = emptyList(), + @SerialName("max_tokens") val maxTokens: Int = 16, + @SerialName("presence_penalty") val presencePenalty: Double = 0.0, + @SerialName("frequency_penalty") val frequencyPenalty: Double = 0.0, + @SerialName("logit_bias") val logitBias: Map = emptyMap(), + val user: String = "xef" ) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt index 1a2c92bb1..2677c0567 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -1,30 +1,46 @@ package com.xebia.functional.xef.server.http.routes -import com.xebia.functional.xef.auto.ai -import com.xebia.functional.xef.auto.llm.openai.getOrThrow -import com.xebia.functional.xef.auto.llm.openai.promptMessage +import com.aallam.openai.api.BetaOpenAI +import com.aallam.openai.api.chat.ChatCompletionRequest +import com.aallam.openai.api.chat.ChatRole +import com.xebia.functional.xef.auto.CoreAIScope +import com.xebia.functional.xef.auto.PromptConfiguration +import com.xebia.functional.xef.auto.llm.openai.* +import com.xebia.functional.xef.auto.llm.openai.OpenAI.Companion.DEFAULT_CHAT +import com.xebia.functional.xef.llm.Chat +import com.xebia.functional.xef.llm.models.chat.Message +import com.xebia.functional.xef.llm.models.chat.Role import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.util.pipeline.* +import com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest as XefChatCompletionRequest +@OptIn(BetaOpenAI::class) fun Routing.routes() { - post("/prompt/message") { - val provider = call.request.headers["provider"]?.let { - it - } ?: "OpenAI" - println("provider: $provider") - val data = call.receive() + post("/chat/completions") { + val model: Chat = call.request.headers["xef-model"]?.let { + it.toOpenAIModel() + } ?: DEFAULT_CHAT + val scope = CoreAIScope(OpenAIEmbeddings(OpenAI.DEFAULT_EMBEDDING)) + val data = call.receive().toCore() response { - ai { - promptMessage(data.message) - }.getOrThrow() + model.promptMessage( + question = data.messages.joinToString("\n") { "${it.role}: ${it.content}" }, + context = scope.context, + promptConfiguration = PromptConfiguration( + temperature = data.temperature, + numberOfPredictions = data.n, + user = data.user ?: "" + ) + ) } } } + /** * Responds with the data and converts any potential Throwable into a 404. */ @@ -35,3 +51,29 @@ private suspend inline fun PipelineContext<*, A }) { call.respondText(it.message ?: "Response not found", status = HttpStatusCode.NotFound) } + +@OptIn(BetaOpenAI::class) +private fun ChatCompletionRequest.toCore(): XefChatCompletionRequest = XefChatCompletionRequest( + model = model.id, + messages = messages.map { Message(it.role.toCore(), it.content ?: "", it.name ?: "") }, + temperature = temperature ?: 0.0, + topP = topP ?: 1.0, + n = n ?: 1, + stream = false, + stop = stop, + maxTokens = maxTokens, + presencePenalty = presencePenalty ?: 0.0, + frequencyPenalty = frequencyPenalty ?: 0.0, + logitBias = logitBias ?: emptyMap(), + user = user, + streamToStandardOut = false +) + +@OptIn(BetaOpenAI::class) +private fun ChatRole.toCore(): Role = + when (this) { + ChatRole.System -> Role.SYSTEM + ChatRole.User -> Role.USER + ChatRole.Assistant -> Role.ASSISTANT + else -> Role.ASSISTANT + } From a0e2f2bb2d659b26657188e9d3a4c104a0a42512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Wed, 9 Aug 2023 08:56:49 +0200 Subject: [PATCH 6/8] added authentication --- gradle/libs.versions.toml | 1 + server/build.gradle.kts | 1 + .../com/xebia/functional/xef/server/Main.kt | 8 +++++ .../xef/server/http/routes/Routes.kt | 34 +++++++++++-------- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7d4b84aa..1ca556221 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,6 +58,7 @@ ktor-client-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json" ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } +ktor-server-auth = { module = "io.ktor:ktor-server-auth", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 3876217a1..1ae876901 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(libs.kotlinx.serialization.json) implementation(libs.logback) implementation(libs.klogging) + implementation(libs.ktor.server.auth) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.core) implementation(libs.ktor.server.contentNegotiation) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt index 5cd8f10d5..33b9d53bd 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Main.kt @@ -7,6 +7,7 @@ import arrow.continuations.ktor.server import com.xebia.functional.xef.server.http.routes.routes import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import io.ktor.server.auth.* import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* @@ -25,6 +26,13 @@ object Main { } install(ContentNegotiation) { json() } install(Resources) + install(Authentication) { + bearer("auth-bearer") { + authenticate { tokenCredential -> + UserIdPrincipal(tokenCredential.token) + } + } + } routing { routes() } } awaitCancellation() diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt index 2677c0567..358a2ec11 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -12,6 +12,7 @@ import com.xebia.functional.xef.llm.models.chat.Message import com.xebia.functional.xef.llm.models.chat.Role import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.auth.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -20,22 +21,25 @@ import com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest as XefChat @OptIn(BetaOpenAI::class) fun Routing.routes() { - post("/chat/completions") { - val model: Chat = call.request.headers["xef-model"]?.let { - it.toOpenAIModel() - } ?: DEFAULT_CHAT - val scope = CoreAIScope(OpenAIEmbeddings(OpenAI.DEFAULT_EMBEDDING)) - val data = call.receive().toCore() - response { - model.promptMessage( - question = data.messages.joinToString("\n") { "${it.role}: ${it.content}" }, - context = scope.context, - promptConfiguration = PromptConfiguration( - temperature = data.temperature, - numberOfPredictions = data.n, - user = data.user ?: "" + authenticate("auth-bearer") { + post("/chat/completions") { + val model: Chat = call.request.headers["xef-model"]?.let { + it.toOpenAIModel() + } ?: DEFAULT_CHAT + val token = call.principal()?.name ?: throw IllegalArgumentException("No token found") + val scope = CoreAIScope(OpenAIEmbeddings(OpenAI(token).TEXT_EMBEDDING_ADA_002)) + val data = call.receive().toCore() + response { + model.promptMessage( + question = data.messages.joinToString("\n") { "${it.role}: ${it.content}" }, + context = scope.context, + promptConfiguration = PromptConfiguration( + temperature = data.temperature, + numberOfPredictions = data.n, + user = data.user ?: "" + ) ) - ) + } } } } From 08046787ccb4f800227cfcf6f01ee5895e1a7a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Wed, 9 Aug 2023 09:52:12 +0200 Subject: [PATCH 7/8] updated model --- .../com/xebia/functional/xef/server/http/routes/Routes.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt index 358a2ec11..4c1fd6842 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -27,7 +27,7 @@ fun Routing.routes() { it.toOpenAIModel() } ?: DEFAULT_CHAT val token = call.principal()?.name ?: throw IllegalArgumentException("No token found") - val scope = CoreAIScope(OpenAIEmbeddings(OpenAI(token).TEXT_EMBEDDING_ADA_002)) + val scope = CoreAIScope(OpenAIEmbeddings(OpenAI(token).GPT_3_5_TURBO_16K)) val data = call.receive().toCore() response { model.promptMessage( From 6acfddebbaef1e7c6fe27d24e4b6a366054e5fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Carlos=20Montan=CC=83ez?= Date: Wed, 9 Aug 2023 20:36:31 +0200 Subject: [PATCH 8/8] refactor and clean up --- .../xef/server/http/routes/Requests.kt | 38 ------------------- .../xef/server/http/routes/Routes.kt | 4 +- 2 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt deleted file mode 100644 index 370e305fe..000000000 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Requests.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.xebia.functional.xef.server.http.routes - -import io.ktor.resources.* -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ChatMessage( - val role: ChatRole, val content: String? = null, val name: String? = null -) - -@JvmInline -@Serializable -value class ChatRole(val role: String) { - public companion object { - public val System: ChatRole = ChatRole("system") - public val User: ChatRole = ChatRole("user") - public val Assistant: ChatRole = ChatRole("assistant") - public val Function: ChatRole = ChatRole("function") - } -} - - -@Serializable -@Resource("/chat/completions") -data class PromptMessageRequest( - val model: String, - val messages: List, - val temperature: Double = 1.0, - @SerialName("top_p") val topP: Double = 1.0, - val n: Int = 1, - val stop: List = emptyList(), - @SerialName("max_tokens") val maxTokens: Int = 16, - @SerialName("presence_penalty") val presencePenalty: Double = 0.0, - @SerialName("frequency_penalty") val frequencyPenalty: Double = 0.0, - @SerialName("logit_bias") val logitBias: Map = emptyMap(), - val user: String = "xef" -) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt index 4c1fd6842..04e27c2cc 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/Routes.kt @@ -23,9 +23,7 @@ import com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest as XefChat fun Routing.routes() { authenticate("auth-bearer") { post("/chat/completions") { - val model: Chat = call.request.headers["xef-model"]?.let { - it.toOpenAIModel() - } ?: DEFAULT_CHAT + val model: Chat = call.request.headers["xef-model"]?.toOpenAIModel() ?: DEFAULT_CHAT val token = call.principal()?.name ?: throw IllegalArgumentException("No token found") val scope = CoreAIScope(OpenAIEmbeddings(OpenAI(token).GPT_3_5_TURBO_16K)) val data = call.receive().toCore()