diff --git a/.github/workflows/create-version-tag.yml b/.github/workflows/create-version-tag.yml index 8be17b4a3..630caef92 100644 --- a/.github/workflows/create-version-tag.yml +++ b/.github/workflows/create-version-tag.yml @@ -23,7 +23,7 @@ jobs: - name: Build uses: gradle/gradle-build-action@v3 with: - arguments: :xef-openai-client-generator:openaiClientGenerate build + arguments: build - name: Read version id: read-version diff --git a/.github/workflows/openai-api-check.yml b/.github/workflows/openai-api-check.yml index 2bb319f84..9db55909d 100644 --- a/.github/workflows/openai-api-check.yml +++ b/.github/workflows/openai-api-check.yml @@ -50,7 +50,7 @@ jobs: const octokit = new Octokit(); const owner = ''xebia-functional''; const repo = ''xef''; - const filePath = ''openai-client/generator/config/openai-api-commit''; + const filePath = ''core/openai-api-commit''; const branch = ''main''; const response = await octokit.repos.getContent({ owner, @@ -78,7 +78,7 @@ jobs: const octokit = new Octokit(); const owner = ''xebia-functional''; const repo = ''xef''; - const filePath = ''openai-client/generator/config/openai-api-commit''; + const filePath = ''core/openai-api-commit''; const existing = await octokit.pulls.list({ owner, repo, @@ -121,7 +121,7 @@ jobs: - name: Update commit if: env.CURRENT_API_COMMIT != env.LATEST_API_COMMIT run: | - echo $LATEST_API_COMMIT > $GITHUB_WORKSPACE/openai-client/generator/config/openai-api-commit + echo $LATEST_API_COMMIT > $GITHUB_WORKSPACE/core/openai-api-commit - name: Set up Java if: env.CURRENT_API_COMMIT != env.LATEST_API_COMMIT uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-development-version.yml b/.github/workflows/publish-development-version.yml index 1d0aa7895..7406faab7 100644 --- a/.github/workflows/publish-development-version.yml +++ b/.github/workflows/publish-development-version.yml @@ -34,7 +34,7 @@ jobs: - name: Assemble uses: gradle/gradle-build-action@v3 with: - arguments: :xef-openai-client-generator:openaiClientGenerate assemble + arguments: assemble - name: Upload reports if: failure() diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b324b6eb4..9498adf8e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: - name: Assemble uses: gradle/gradle-build-action@v3 with: - arguments: :xef-openai-client-generator:openaiClientGenerate assemble + arguments: assemble - name: Upload reports if: failure() diff --git a/build.gradle.kts b/build.gradle.kts index 0dc19a5bd..594022ff9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,12 +28,10 @@ fun Project.configureBuildAndTestTask(taskName: String, moduleType: ModulePlatfo doLast { when (moduleType) { ModulePlatformType.SINGLE -> { - project.exec { commandLine(gradleCommand, ":xef-openai-client-generator:openaiClientGenerate") } val excludedModules = includeOrNotModulesToCommand(multiPlatformModules, platform, false) project.exec { commandLine(gradleCommand, "build", *excludedModules) } } ModulePlatformType.MULTI -> { - project.exec { commandLine(gradleCommand, ":xef-openai-client-generator:openaiClientGenerate") } val includedModules = includeOrNotModulesToCommand(multiPlatformModules, platform, true) project.exec { commandLine(gradleCommand, *includedModules) } } @@ -65,3 +63,7 @@ fun getGradleCommand(platform: String): String { configureBuildAndTestTask("buildAndTestMultip", ModulePlatformType.MULTI) configureBuildAndTestTask("buildAndTestSinglep", ModulePlatformType.SINGLE) +tasks.register("downloadOpenAIAPI") { + input.set(file("core/openai-api-commit")) + output.set(file("core/openai-api.yaml")) +} diff --git a/buildSrc/src/main/kotlin/DownloadOpenAI.kt b/buildSrc/src/main/kotlin/DownloadOpenAI.kt new file mode 100644 index 000000000..09900038f --- /dev/null +++ b/buildSrc/src/main/kotlin/DownloadOpenAI.kt @@ -0,0 +1,45 @@ +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.* +import org.gradle.api.tasks.options.Option +import java.net.URL +import java.nio.channels.Channels + +@CacheableTask +abstract class DownloadOpenAI : DefaultTask() { + init { + description = "Download the OpenAI API client" + group = "openai" + } + + @get:InputFile + @get:Option(description = "Commit hash of the fetched OpenAI OpenAPI Yaml file") + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val input: RegularFileProperty + + @get:OutputFile + @get:Option(description = "Path of file to store the fetched OpenAI OpenAPI Yaml") + abstract val output: RegularFileProperty + + @TaskAction + fun download() { + val commit = readCommit() + downloadAPI(commit) + } + + private fun readCommit(): String { + val asFile = input.get().asFile + val readText = asFile.readText() + require(readText.isNotEmpty()) { "${asFile.path} Commit hash is empty" } + return readText.trim { it <= ' ' } + } + + private fun downloadAPI(commit: String) { + val url = URL("https://raw.githubusercontent.com/openai/openai-openapi/$commit/openapi.yaml") + Channels.newChannel(url.openStream()).use { channel -> + output.asFile.get().outputStream().use { output -> + output.channel.transferFrom(channel, 0, Long.MAX_VALUE) + } + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index de1530459..d7580df54 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -16,6 +16,13 @@ plugins { alias(libs.plugins.arrow.gradle.publish) alias(libs.plugins.semver.gradle) alias(libs.plugins.detekt) + alias(libs.plugins.openapi.kt) +} + +openApiConfig { + spec("OpenAI", file("openai-api.yaml")) { + packageName = "com.xebia.functional.xef.openapi" + } } dependencies { @@ -47,11 +54,11 @@ kotlin { classpath = compileDependencyFiles + runtimeDependencyFiles + output.allOutputs testClassesDirs = output.classesDirs testLogging { events("passed") } - } - } - val test by compilations.getting - integrationTest.associateWith(test) - } + } + } + val test by compilations.getting + integrationTest.associateWith(test) + } } js(IR) { browser() @@ -62,73 +69,72 @@ kotlin { macosArm64() mingwX64() sourceSets { - all { - languageSettings.optIn("kotlin.ExperimentalStdlibApi") - } - val commonMain by getting { - dependencies { - api(libs.bundles.arrow) - api(libs.kotlinx.serialization.json) - api(libs.kotlinx.serialization.yaml) - api(libs.ktor.utils) - api(projects.xefTokenizer) - api(projects.xefOpenaiClient) - implementation(libs.bundles.ktor.client) - implementation(libs.klogging) - implementation(libs.uuid) - implementation(libs.ktor.client.logging) - implementation(libs.klogging) - } - } - val commonTest by getting { - dependencies { - implementation(libs.kotest.property) - implementation(libs.kotest.framework) - implementation(libs.kotest.assertions) - } - } - val jvmMain by getting { - dependencies { - implementation(libs.ktor.http) - implementation(libs.rss.reader) - api(libs.jackson) - api(libs.jackson.schema) - api(libs.jackson.schema.jakarta) - api(libs.jakarta.validation) - implementation(libs.kotlinx.coroutines.reactive) - api(libs.ktor.client.cio) - } - } - val jsMain by getting { - dependencies { - api(libs.ktor.client.js) - } - } - val jvmTest by getting { - dependencies { - implementation(libs.kotest.junit5) - } - } - val linuxX64Main by getting { - dependencies { - implementation(libs.ktor.client.cio) - } - } - val macosX64Main by getting { - dependencies { - implementation(libs.ktor.client.cio) - } - } - val macosArm64Main by getting { - dependencies { - implementation(libs.ktor.client.cio) - } - } - val mingwX64Main by getting { - dependencies { - implementation(libs.ktor.client.winhttp) - } - } + all { + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + } + val commonMain by getting { + dependencies { + api(libs.bundles.arrow) + api(libs.kotlinx.serialization.json) + api(libs.kotlinx.serialization.yaml) + api(libs.ktor.utils) + api(projects.xefTokenizer) + implementation(libs.bundles.ktor.client) + implementation(libs.klogging) + implementation(libs.uuid) + implementation(libs.ktor.client.logging) + implementation(libs.klogging) + } + } + val commonTest by getting { + dependencies { + implementation(libs.kotest.property) + implementation(libs.kotest.framework) + implementation(libs.kotest.assertions) + } + } + val jvmMain by getting { + dependencies { + implementation(libs.ktor.http) + implementation(libs.rss.reader) + api(libs.jackson) + api(libs.jackson.schema) + api(libs.jackson.schema.jakarta) + api(libs.jakarta.validation) + implementation(libs.kotlinx.coroutines.reactive) + api(libs.ktor.client.cio) + } + } + val jsMain by getting { + dependencies { + api(libs.ktor.client.js) + } + } + val jvmTest by getting { + dependencies { + implementation(libs.kotest.junit5) + } + } + val linuxX64Main by getting { + dependencies { + implementation(libs.ktor.client.cio) + } + } + val macosX64Main by getting { + dependencies { + implementation(libs.ktor.client.cio) + } + } + val macosArm64Main by getting { + dependencies { + implementation(libs.ktor.client.cio) + } + } + val mingwX64Main by getting { + dependencies { + implementation(libs.ktor.client.winhttp) + } + } } } diff --git a/core/openai-api-commit b/core/openai-api-commit new file mode 100644 index 000000000..4746a6be6 --- /dev/null +++ b/core/openai-api-commit @@ -0,0 +1 @@ +df5699f5c48fe455d9d036198e77fdb637edee29 \ No newline at end of file diff --git a/openai-client/generator/config/openai-api.yaml b/core/openai-api.yaml similarity index 100% rename from openai-client/generator/config/openai-api.yaml rename to core/openai-api.yaml diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/AI.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/AI.kt index b665a76aa..c3a44a099 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/AI.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/AI.kt @@ -1,13 +1,15 @@ package com.xebia.functional.xef -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequest import com.xebia.functional.xef.conversation.AiDsl import com.xebia.functional.xef.llm.models.modelType import com.xebia.functional.xef.llm.prompt import com.xebia.functional.xef.llm.promptStreaming +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import com.xebia.functional.xef.prompt.Prompt import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive class AI(private val config: AIConfig, val serializer: Tool) { @@ -59,17 +61,19 @@ class AI(private val config: AIConfig, val serializer: Tool) { val encoding = prompt.model.modelType(forFunctions = false).encoding val cases = serializer.cases val logitBias = - cases - .flatMap { - val result = encoding.encode(it.function.name) - if (result.size > 1) { - error("Cannot encode enum case $it into one token") + JsonObject( + cases + .flatMap { + val result = encoding.encode(it.function.name) + if (result.size > 1) { + error("Cannot encode enum case $it into one token") + } + result } - result - } - .associate { "$it" to 100 } + .associate { "$it" to JsonPrimitive(100) } + ) val result = - config.api.createChatCompletion( + config.api.completions.createChatCompletion( CreateChatCompletionRequest( messages = prompt.messages, model = prompt.model, diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/AIConfig.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/AIConfig.kt index a9d0f945f..a44be8b9a 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/AIConfig.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/AIConfig.kt @@ -1,13 +1,13 @@ package com.xebia.functional.xef -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.api.OpenAI -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.conversation.Conversation +import com.xebia.functional.xef.openapi.Chat +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.OpenAI data class AIConfig( val tools: List> = emptyList(), - val model: CreateChatCompletionRequestModel = CreateChatCompletionRequestModel.gpt_4o, + val model: CreateChatCompletionRequest.Model = CreateChatCompletionRequest.Model.Gpt4o, val config: Config = Config(), val openAI: OpenAI = OpenAI(config, logRequests = false), val api: Chat = openAI.chat, diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/Config.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/Config.kt index 2f1cb2932..8e627dcb7 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/Config.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/Config.kt @@ -1,9 +1,8 @@ package com.xebia.functional.xef import arrow.core.nonEmptyListOf -import com.xebia.functional.openai.Config as OpenAIConfig -import com.xebia.functional.openai.generated.api.OpenAI import com.xebia.functional.xef.env.getenv +import com.xebia.functional.xef.openapi.OpenAI import io.ktor.client.* import io.ktor.client.engine.* import io.ktor.client.plugins.* @@ -74,15 +73,5 @@ fun OpenAI( } } val client = httpClientEngine?.let { HttpClient(it, clientConfig) } ?: HttpClient(clientConfig) - return OpenAI( - client, - OpenAIConfig( - baseUrl = config.baseUrl, - token = token, - org = config.org, - json = config.json, - streamingPrefix = config.streamingPrefix, - streamingDelimiter = config.streamingDelimiter - ) - ) + return OpenAI(client) } diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/Tool.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/Tool.kt index a0b20a62e..0db21d15b 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/Tool.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/Tool.kt @@ -1,10 +1,10 @@ package com.xebia.functional.xef -import com.xebia.functional.openai.generated.model.FunctionObject import com.xebia.functional.xef.conversation.Description import com.xebia.functional.xef.llm.FunctionCall import com.xebia.functional.xef.llm.StreamedFunction import com.xebia.functional.xef.llm.chatFunction +import com.xebia.functional.xef.openapi.FunctionObject import kotlin.jvm.JvmName import kotlin.reflect.KClass import kotlin.reflect.KFunction1 diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Chat.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Chat.kt index f8a39155f..e1cca7998 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Chat.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Chat.kt @@ -1,9 +1,5 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequest -import com.xebia.functional.openai.generated.model.CreateChatCompletionResponse -import com.xebia.functional.openai.generated.model.CreateChatCompletionResponseChoicesInner import com.xebia.functional.xef.AIError import com.xebia.functional.xef.Tool import com.xebia.functional.xef.conversation.AiDsl @@ -11,6 +7,9 @@ import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.llm.models.MessageWithUsage import com.xebia.functional.xef.llm.models.MessagesUsage import com.xebia.functional.xef.llm.models.MessagesWithUsage +import com.xebia.functional.xef.openapi.Chat +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.CreateChatCompletionResponse import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder import com.xebia.functional.xef.store.Memory @@ -38,7 +37,8 @@ fun Chat.promptStreaming( val buffer = StringBuilder() - this@promptStreaming.createChatCompletionStream(request) + completions + .createChatCompletionStream(request) .mapNotNull { val content = it.choices.firstOrNull()?.delta?.content if (content != null) { @@ -88,7 +88,7 @@ suspend fun Chat.promptMessagesAndUsage( private suspend fun Chat.promptResponse( prompt: Prompt, scope: Conversation = Conversation(), - block: suspend Chat.(CreateChatCompletionResponseChoicesInner) -> T? + block: suspend Chat.(CreateChatCompletionResponse.Choices) -> T? ): Pair, CreateChatCompletionResponse> = scope.metric.promptSpan(prompt) { val promptMemories: List = prompt.messages.toMemory(scope) @@ -107,7 +107,7 @@ private suspend fun Chat.promptResponse( seed = adaptedPrompt.configuration.seed, ) - val createResponse: CreateChatCompletionResponse = createChatCompletion(request) + val createResponse = completions.createChatCompletion(request) Pair( createResponse .addMetrics(scope) diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/ChatWithFunctions.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/ChatWithFunctions.kt index 269b23da3..7cca85a0c 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/ChatWithFunctions.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/ChatWithFunctions.kt @@ -3,14 +3,17 @@ package com.xebia.functional.xef.llm import arrow.core.nonFatalOrThrow import arrow.core.raise.catch import arrow.fx.coroutines.parMapNotNull -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.AIError import com.xebia.functional.xef.AIEvent import com.xebia.functional.xef.Tool import com.xebia.functional.xef.conversation.AiDsl import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.llm.models.functions.buildJsonSchema +import com.xebia.functional.xef.openapi.* +import com.xebia.functional.xef.openapi.Chat +import com.xebia.functional.xef.openapi.ChatCompletionToolChoiceOption.NoneOrAutoOrRequired.Auto +import com.xebia.functional.xef.openapi.ChatCompletionToolChoiceOption.NoneOrAutoOrRequired.Required +import com.xebia.functional.xef.openapi.FunctionObject import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.tool import io.github.oshai.kotlinlogging.KotlinLogging @@ -155,17 +158,17 @@ private fun assistantRequestedCallMessage( ): ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage( ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, toolCalls = calls.map { ChatCompletionMessageToolCall( id = it.callId, function = - ChatCompletionMessageToolCallFunction( + ChatCompletionMessageToolCall.Function( name = it.functionName, arguments = it.arguments ), - type = ChatCompletionMessageToolCall.Type.function + type = ChatCompletionMessageToolCall.Type.Function ) } ) @@ -229,7 +232,7 @@ private suspend fun Chat.promptWithResponse( val request = createChatCompletionRequest(adaptedPrompt) tryDeserialize(serializer, promptWithFunctions.configuration.maxDeserializationAttempts) { val requestedMemories = prompt.messages.toMemory(scope) - val response = createChatCompletion(request).addMetrics(scope) + val response = completions.createChatCompletion(request).addMetrics(scope) response.choices.addChoiceWithFunctionsToMemory( scope, requestedMemories, @@ -256,20 +259,20 @@ private fun chatCompletionToolChoiceOption(adaptedPrompt: Prompt): ChatCompletio if (adaptedPrompt.functions.size == 1) ChatCompletionToolChoiceOption.CaseChatCompletionNamedToolChoice( ChatCompletionNamedToolChoice( - type = ChatCompletionNamedToolChoice.Type.function, + type = ChatCompletionNamedToolChoice.Type.Function, // TODO review access to first - function = ChatCompletionNamedToolChoiceFunction(adaptedPrompt.functions.first().name) + function = ChatCompletionNamedToolChoice.Function(adaptedPrompt.functions.first().name) ) ) else { - if (adaptedPrompt.model is CreateChatCompletionRequestModel.Custom) - ChatCompletionToolChoiceOption.CaseString("auto") - else ChatCompletionToolChoiceOption.CaseString("required") + if (adaptedPrompt.model is CreateChatCompletionRequest.Model.OpenCase) + ChatCompletionToolChoiceOption.CaseNoneOrAutoOrRequired(Auto) + else ChatCompletionToolChoiceOption.CaseNoneOrAutoOrRequired(Required) } private fun chatCompletionTools(adaptedPrompt: Prompt): List = adaptedPrompt.functions.map { - ChatCompletionTool(type = ChatCompletionTool.Type.function, function = it) + ChatCompletionTool(type = ChatCompletionTool.Type.Function, function = it) } @AiDsl diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Embeddings.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Embeddings.kt index 981d52fa5..d68810e6c 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Embeddings.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Embeddings.kt @@ -1,17 +1,13 @@ package com.xebia.functional.xef.llm import arrow.fx.coroutines.parMap -import com.xebia.functional.openai.generated.api.Embeddings -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequest -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestInput -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel -import com.xebia.functional.openai.generated.model.Embedding +import com.xebia.functional.xef.openapi.* suspend fun Embeddings.embedDocuments( texts: List, chunkSize: Int = 400, - embeddingRequestModel: CreateEmbeddingRequestModel = - CreateEmbeddingRequestModel.text_embedding_ada_002 + embeddingRequestModel: CreateEmbeddingRequest.Model = + CreateEmbeddingRequest.Model.TextEmbeddingAda002 ): List = if (texts.isEmpty()) emptyList() else @@ -21,7 +17,7 @@ suspend fun Embeddings.embedDocuments( createEmbedding( CreateEmbeddingRequest( model = embeddingRequestModel, - input = CreateEmbeddingRequestInput.CaseStrings(it) + input = CreateEmbeddingRequest.Input.CaseStrings(it) ) ) .data @@ -30,7 +26,7 @@ suspend fun Embeddings.embedDocuments( suspend fun Embeddings.embedQuery( text: String, - embeddingRequestModel: CreateEmbeddingRequestModel + embeddingRequestModel: CreateEmbeddingRequest.Model ): List = if (text.isNotEmpty()) embedDocuments(texts = listOf(text), embeddingRequestModel = embeddingRequestModel) diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/LLM.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/LLM.kt index 928de8425..fe79bf5c8 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/LLM.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/LLM.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage import com.xebia.functional.tokenizer.Encoding import com.xebia.functional.tokenizer.ModelType +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage import com.xebia.functional.xef.prompt.completionRole import com.xebia.functional.xef.prompt.contentAsString diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MemoryManagement.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MemoryManagement.kt index 9ea8a63ed..df9a65249 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MemoryManagement.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MemoryManagement.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.conversation.MessagesToHistory +import com.xebia.functional.xef.openapi.* import com.xebia.functional.xef.store.ConversationId import com.xebia.functional.xef.store.MemorizedMessage import com.xebia.functional.xef.store.Memory @@ -32,11 +32,11 @@ internal fun List.toMemory(scope: Conversation): L } else emptyList() } -internal suspend fun List.addChoiceWithFunctionsToMemory( +internal suspend fun List.addChoiceWithFunctionsToMemory( scope: Conversation, previousMemories: List, history: MessagesToHistory -): List = also { +): List = also { val cid = scope.conversationId if (history != MessagesToHistory.NONE && isNotEmpty() && cid != null) { val aiMemory = this.map { it.message.toMemory(cid, scope.store.incrementIndexAndGet()) } @@ -45,11 +45,11 @@ internal suspend fun List.addChoiceWit } } -internal suspend fun List.addChoiceToMemory( +internal suspend fun List.addChoiceToMemory( scope: Conversation, previousMemories: List, history: MessagesToHistory -): List = also { +): List = also { val cid = scope.conversationId if (history != MessagesToHistory.NONE && isNotEmpty() && cid != null) { val aiMemory = @@ -65,10 +65,10 @@ suspend fun VectorStore.addMemoriesByHistory(history: MessagesToHistory, memorie addMemories(memories) } MessagesToHistory.ONLY_SYSTEM_MESSAGES -> { - addMemories(memories.filter { it.content.role == ChatCompletionRole.system }) + addMemories(memories.filter { it.content.role == ChatCompletionRole.System }) } MessagesToHistory.NOT_SYSTEM_MESSAGES -> { - addMemories(memories.filter { it.content.role != ChatCompletionRole.system }) + addMemories(memories.filter { it.content.role != ChatCompletionRole.System }) } MessagesToHistory.NONE -> {} } diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MetricManagement.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MetricManagement.kt index 91d8e948a..806e42c53 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MetricManagement.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/MetricManagement.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.model.CreateChatCompletionResponse -import com.xebia.functional.openai.generated.model.MessageObject -import com.xebia.functional.openai.generated.model.RunObject -import com.xebia.functional.openai.generated.model.RunStepObject import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.llm.assistants.RunDelta import com.xebia.functional.xef.metrics.Metric +import com.xebia.functional.xef.openapi.CreateChatCompletionResponse +import com.xebia.functional.xef.openapi.MessageObject +import com.xebia.functional.xef.openapi.RunObject +import com.xebia.functional.xef.openapi.RunStepObject import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.completionRole import com.xebia.functional.xef.prompt.contentAsString diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/PromptCalculator.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/PromptCalculator.kt index f8053056f..02d81d490 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/PromptCalculator.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/PromptCalculator.kt @@ -1,10 +1,10 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage import com.xebia.functional.xef.AIError import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.conversation.MessagesFromHistory import com.xebia.functional.xef.llm.models.modelType +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.store.Memory diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/StreamedFunction.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/StreamedFunction.kt index 6164b8a1d..e9d454957 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/StreamedFunction.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/StreamedFunction.kt @@ -1,9 +1,8 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.llm.StreamedFunction.Companion.PropertyType.* +import com.xebia.functional.xef.openapi.* import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder import kotlin.jvm.JvmSynthetic @@ -48,7 +47,7 @@ sealed class StreamedFunction { ) { val messages = mutableListOf() // this function call is mutable and will be updated as the stream progresses - var functionCall = ChatCompletionMessageToolCallFunction("", "") + var functionCall = ChatCompletionMessageToolCall.Function("", "") // the current property is mutable and will be updated as the stream progresses var currentProperty: String? = null // we keep track to not emit the same property multiple times @@ -61,7 +60,7 @@ sealed class StreamedFunction { // as the LLM is sending us chunks with malformed JSON if (schema != null) { val example = createExampleFromSchema(schema) - chat + chat.completions .createChatCompletionStream(request) .onCompletion { val newMessages = prompt.messages + messages @@ -128,7 +127,7 @@ sealed class StreamedFunction { } private suspend fun FlowCollector>.streamResult( - functionCall: ChatCompletionMessageToolCallFunction, + functionCall: ChatCompletionMessageToolCall.Function, messages: MutableList, serializer: suspend (json: String) -> A ) { @@ -290,13 +289,13 @@ sealed class StreamedFunction { } private fun mergeArgumentsWithDelta( - functionCall: ChatCompletionMessageToolCallFunction, + functionCall: ChatCompletionMessageToolCall.Function, functionCall0: ChatCompletionMessageToolCallChunk - ): ChatCompletionMessageToolCallFunction = + ): ChatCompletionMessageToolCall.Function = functionCall.copy(arguments = functionCall.arguments + (functionCall0.function?.arguments)) private fun getLastReferencedPropertyInArguments( - functionCall: ChatCompletionMessageToolCallFunction + functionCall: ChatCompletionMessageToolCall.Function ): String? = """"(.*?)":""" .toRegex() diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Vision.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Vision.kt index 38d878838..efe94eede 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Vision.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Vision.kt @@ -1,11 +1,8 @@ package com.xebia.functional.xef.llm -import com.xebia.functional.openai.UploadFile -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.Image import com.xebia.functional.xef.Tool import com.xebia.functional.xef.conversation.Conversation +import com.xebia.functional.xef.openapi.* import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.image import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user @@ -21,9 +18,9 @@ suspend inline fun Chat.visionStructured( prompt: String, url: String, conversation: Conversation = Conversation(), - model: CreateChatCompletionRequestModel = CreateChatCompletionRequestModel.gpt_4_vision_preview, - functionsModel: CreateChatCompletionRequestModel = - CreateChatCompletionRequestModel.gpt_3_5_turbo_0125, + model: CreateChatCompletionRequest.Model = CreateChatCompletionRequest.Model.Gpt4VisionPreview, + functionsModel: CreateChatCompletionRequest.Model = + CreateChatCompletionRequest.Model.Gpt35Turbo0125, tools: List> = emptyList() ): A { val response = vision(prompt, url, model, conversation).toList().joinToString("") { it } @@ -38,7 +35,7 @@ suspend inline fun Chat.visionStructured( fun Chat.vision( prompt: String, url: String, - model: CreateChatCompletionRequestModel = CreateChatCompletionRequestModel.gpt_4_vision_preview, + model: CreateChatCompletionRequest.Model = CreateChatCompletionRequest.Model.Gpt4VisionPreview, conversation: Conversation = Conversation() ): Flow = promptStreaming( diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Assistant.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Assistant.kt index 5d84096d4..994e67d91 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Assistant.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Assistant.kt @@ -1,11 +1,11 @@ package com.xebia.functional.xef.llm.assistants -import com.xebia.functional.openai.generated.api.Assistants -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.llm.assistants.AssistantThread.Companion.defaultConfig import com.xebia.functional.xef.llm.models.functions.buildJsonSchema +import com.xebia.functional.xef.openapi.* +import com.xebia.functional.xef.openapi.Assistants import io.ktor.util.logging.* import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -74,12 +74,12 @@ class Assistant( @Serializable data class ToolOutput(val schema: JsonObject, val result: JsonElement) suspend operator fun invoke( - model: CreateAssistantRequestModel, + model: CreateAssistantRequest.Model, name: String? = null, description: String? = null, instructions: String? = null, - tools: List = arrayListOf(), - toolResources: CreateAssistantRequestToolResources? = null, + tools: List = arrayListOf(), + toolResources: CreateAssistantRequest.ToolResources? = null, metadata: JsonObject? = null, toolsConfig: List> = emptyList(), config: Config = Config(), @@ -120,19 +120,19 @@ class Assistant( val fileIds = parsed["file_ids"]?.let { (it as List<*>).map { it.toString() } } val vectorStoreIds = parsed["vector_store_ids"]?.let { (it as List<*>).map { it.toString() } } val toolResourcesRequest = - CreateAssistantRequestToolResources( + CreateAssistantRequest.ToolResources( codeInterpreter = - fileIds?.let { CreateAssistantRequestToolResourcesCodeInterpreter(fileIds = it) }, + fileIds?.let { CreateAssistantRequest.ToolResources.CodeInterpreter(fileIds = it) }, fileSearch = vectorStoreIds?.let { - CreateAssistantRequestToolResourcesFileSearch(vectorStoreIds = it) + CreateAssistantRequest.ToolResources.FileSearch(vectorStoreIds = it) } ) val assistantRequest = AssistantRequest( assistantId = parsed["assistant_id"]?.literalContentOrNull, model = - parsed["model"]?.literalContentOrNull?.let { CreateAssistantRequestModel.Custom(it) } + parsed["model"]?.literalContentOrNull?.let { CreateAssistantRequest.Model.OpenCase(it) } ?: error("model is required"), name = parsed["name"]?.literalContentOrNull, description = parsed["description"]?.literalContentOrNull, @@ -163,7 +163,7 @@ class Assistant( functionObject.name, functionObject.description ?: "", functionObject.parameters?.let { el -> - Json.encodeToString(JsonObject.serializer(), el) + Json.encodeToString(JsonElement.serializer(), el) } ?: "" ) } else { @@ -194,16 +194,22 @@ class Assistant( name = assistantRequest.name, description = assistantRequest.description, instructions = assistantRequest.instructions, - tools = assistantTools(assistantRequest), + tools = + assistantTools( + assistantRequest, + code = ModifyAssistantRequest.Tools::CaseAssistantToolsCode, + fileSearch = ModifyAssistantRequest.Tools::CaseAssistantToolsFileSearch, + function = ModifyAssistantRequest.Tools::CaseAssistantToolsFunction + ), toolResources = assistantRequest.toolResources?.let { - ModifyAssistantRequestToolResources( + ModifyAssistantRequest.ToolResources( codeInterpreter = it.codeInterpreter?.let { - ModifyAssistantRequestToolResourcesCodeInterpreter(fileIds = it.fileIds) + ModifyAssistantRequest.ToolResources.CodeInterpreter(fileIds = it.fileIds) }, fileSearch = - ModifyAssistantRequestToolResourcesFileSearch( + ModifyAssistantRequest.ToolResources.FileSearch( vectorStoreIds = it.fileSearch?.vectorStoreIds ) ) @@ -219,7 +225,13 @@ class Assistant( name = assistantRequest.name, description = assistantRequest.description, instructions = assistantRequest.instructions, - tools = assistantTools(assistantRequest), + tools = + assistantTools( + assistantRequest, + code = CreateAssistantRequest.Tools::CaseAssistantToolsCode, + fileSearch = CreateAssistantRequest.Tools::CaseAssistantToolsFileSearch, + function = CreateAssistantRequest.Tools::CaseAssistantToolsFunction + ), toolResources = assistantRequest.toolResources, metadata = assistantRequest.metadata @@ -232,23 +244,24 @@ class Assistant( ) } - private fun assistantTools( - assistantRequest: AssistantRequest - ): List = + private fun assistantTools( + assistantRequest: AssistantRequest, + code: (AssistantToolsCode) -> A, + fileSearch: (AssistantToolsFileSearch) -> A, + function: (AssistantToolsFunction) -> A + ): List = assistantRequest.tools.orEmpty().map { when (it) { is AssistantTool.CodeInterpreter -> - AssistantObjectToolsInner.CaseAssistantToolsCode( - AssistantToolsCode(type = AssistantToolsCode.Type.code_interpreter) - ) + code(AssistantToolsCode(type = AssistantToolsCode.Type.CodeInterpreter)) + + // TODO Retrieval seems to be renamed to file search?? is AssistantTool.Retrieval -> - AssistantObjectToolsInner.CaseAssistantToolsFileSearch( - AssistantToolsFileSearch(type = AssistantToolsFileSearch.Type.file_search) - ) + fileSearch(AssistantToolsFileSearch(type = AssistantToolsFileSearch.Type.FileSearch)) is AssistantTool.Function -> - AssistantObjectToolsInner.CaseAssistantToolsFunction( + function( AssistantToolsFunction( - type = AssistantToolsFunction.Type.function, + type = AssistantToolsFunction.Type.Function, function = FunctionObject( name = it.name, diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantRequest.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantRequest.kt index 16d50f0b4..3f9d3dd9f 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantRequest.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantRequest.kt @@ -1,7 +1,6 @@ package com.xebia.functional.xef.llm.assistants -import com.xebia.functional.openai.generated.model.CreateAssistantRequestModel -import com.xebia.functional.openai.generated.model.CreateAssistantRequestToolResources +import com.xebia.functional.xef.openapi.CreateAssistantRequest import kotlinx.serialization.Required import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -9,7 +8,7 @@ import kotlinx.serialization.Serializable @Serializable data class AssistantRequest( @SerialName(value = "assistant_id") val assistantId: String? = null, - @SerialName(value = "model") @Required val model: CreateAssistantRequestModel, + @SerialName(value = "model") @Required val model: CreateAssistantRequest.Model, /* The name of the assistant. The maximum length is 256 characters. */ @SerialName(value = "name") val name: String? = null, @@ -23,7 +22,7 @@ data class AssistantRequest( /* A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `retrieval`, or `function`. */ @SerialName(value = "tools") val tools: List? = arrayListOf(), @SerialName(value = "tool_resources") - val toolResources: CreateAssistantRequestToolResources? = null, + val toolResources: CreateAssistantRequest.ToolResources? = null, /* Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maxium of 512 characters long. */ @SerialName(value = "metadata") val metadata: Map? = null diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantThread.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantThread.kt index 934a1b790..dae87a9d6 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantThread.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/AssistantThread.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.llm.assistants import arrow.fx.coroutines.parMapNotNull -import com.xebia.functional.openai.generated.api.Assistants -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.llm.addMetrics import com.xebia.functional.xef.metrics.Metric +import com.xebia.functional.xef.openapi.* +import com.xebia.functional.xef.openapi.Threads.Messages.ListMessagesOrder import io.ktor.client.request.* import kotlin.jvm.JvmName import kotlinx.coroutines.flow.* @@ -18,7 +18,7 @@ class AssistantThread( val threadId: String, val metric: Metric = Metric.EMPTY, private val config: Config = Config(), - private val api: Assistants = OpenAI(config).assistants + private val api: Threads = OpenAI(config).threads ) { suspend fun delete(): Boolean = @@ -31,29 +31,29 @@ class AssistantThread( createMessage( request = CreateMessageRequest( - role = CreateMessageRequest.Role.user, - content = CreateMessageRequestContent.CaseString(message.content), - attachments = message.fileIds.map { MessageObjectAttachmentsInner(fileId = it) } + role = CreateMessageRequest.Role.User, + content = CreateMessageRequest.Content.CaseString(message.content), + attachments = message.fileIds.map { CreateMessageRequest.Attachments(fileId = it) } ), ) suspend fun createMessage(content: String): MessageObject = createMessage( CreateMessageRequest( - role = CreateMessageRequest.Role.user, - content = CreateMessageRequestContent.CaseString(content) + role = CreateMessageRequest.Role.User, + content = CreateMessageRequest.Content.CaseString(content) ) ) suspend fun createMessage(request: CreateMessageRequest): MessageObject = - api.createMessage(threadId, request, configure = ::defaultConfig) + api.messages.createMessage(threadId, request, configure = ::defaultConfig) suspend fun getMessage(messageId: String): MessageObject = - api.getMessage(threadId, messageId, configure = ::defaultConfig) + api.messages.getMessage(threadId, messageId, configure = ::defaultConfig) data class ThreadMessagesFilter( val limit: Int? = 20, - val order: Assistants.OrderListMessages? = Assistants.OrderListMessages.desc, + val order: ListMessagesOrder? = ListMessagesOrder.Desc, val after: String? = null, val before: String? = null ) @@ -61,7 +61,7 @@ class AssistantThread( suspend fun listMessages( filter: ThreadMessagesFilter = ThreadMessagesFilter() ): List = - api + api.messages .listMessages( threadId = threadId, limit = filter.limit, @@ -76,10 +76,12 @@ class AssistantThread( createRun(CreateRunRequest(assistantId = assistant.assistantId)) suspend fun createRun(request: CreateRunRequest): RunObject = - api.createRun(threadId, request, configure = ::defaultConfig).addMetrics(metric, "RunCreated") + api.runs + .createRun(threadId, request, configure = ::defaultConfig) + .addMetrics(metric, "RunCreated") fun createRunStream(assistant: Assistant, request: CreateRunRequest): Flow = flow { - api + api.runs .createRunStream(threadId, request, configure = ::defaultConfig) .map { RunDelta.fromServerSentEvent(it) } .map { it.addMetrics(metric) } @@ -105,8 +107,8 @@ class AssistantThread( flowCollector: FlowCollector ) { if ( - event.run.status == RunObject.Status.requires_action && - event.run.requiredAction?.type == RunObjectRequiredAction.Type.submit_tool_outputs + event.run.status == RunObject.Status.RequiresAction && + event.run.requiredAction?.type == RunObject.RequiredAction.Type.SubmitToolOutputs ) { val calls = event.run.requiredAction?.submitToolOutputs?.toolCalls.orEmpty() val callsResult: List> = @@ -116,18 +118,18 @@ class AssistantThread( SubmitToolOutputsRunRequest( toolOutputs = results.map { (toolCallId, result) -> - SubmitToolOutputsRunRequestToolOutputsInner( + SubmitToolOutputsRunRequest.ToolOutputs( toolCallId = toolCallId, output = Json.encodeToString(Assistant.Companion.ToolOutput.serializer(), result) ) } ) - api + api.runs.submitToolOutputs .submitToolOuputsToRunStream( threadId = threadId, runId = event.run.id, - submitToolOutputsRunRequest = toolOutputsRequest, + body = toolOutputsRequest, configure = ::defaultConfig ) .collect { @@ -144,21 +146,21 @@ class AssistantThread( val run = getRun(event.run.id) val finalEvent = when (run.status) { - RunObject.Status.queued -> Pair(RunDelta.RunQueued(run), "RunQueued") - RunObject.Status.in_progress -> Pair(RunDelta.RunInProgress(run), "RunInProgress") - RunObject.Status.requires_action -> + RunObject.Status.Queued -> Pair(RunDelta.RunQueued(run), "RunQueued") + RunObject.Status.InProgress -> Pair(RunDelta.RunInProgress(run), "RunInProgress") + RunObject.Status.RequiresAction -> Pair(RunDelta.RunRequiresAction(run), "RunRequiresAction") - RunObject.Status.cancelling -> Pair(RunDelta.RunCancelling(run), "RunCancelling") - RunObject.Status.cancelled -> Pair(RunDelta.RunCancelled(run), "RunCancelled") - RunObject.Status.failed -> Pair(RunDelta.RunFailed(run), "RunFailed") - RunObject.Status.completed -> Pair(RunDelta.RunCompleted(run), "RunCompleted") - RunObject.Status.expired -> Pair(RunDelta.RunExpired(run), "RunExpired") - RunObject.Status.incomplete -> Pair(RunDelta.RunIncomplete(run), "RunIncomplete") + RunObject.Status.Cancelling -> Pair(RunDelta.RunCancelling(run), "RunCancelling") + RunObject.Status.Cancelled -> Pair(RunDelta.RunCancelled(run), "RunCancelled") + RunObject.Status.Failed -> Pair(RunDelta.RunFailed(run), "RunFailed") + RunObject.Status.Completed -> Pair(RunDelta.RunCompleted(run), "RunCompleted") + RunObject.Status.Expired -> Pair(RunDelta.RunExpired(run), "RunExpired") + RunObject.Status.Incomplete -> Pair(RunDelta.RunIncomplete(run), "RunIncomplete") } flowCollector.emit(finalEvent.first) metric.assistantCreateRun(run, finalEvent.second) - if (run.status == RunObject.Status.requires_action) { + if (run.status == RunObject.Status.RequiresAction) { takeRequiredAction( depth + 1, RunDelta.RunRequiresAction(run), @@ -178,13 +180,9 @@ class AssistantThread( val function = toolCall.function val functionName = function.name val functionArguments = function.arguments - return if (functionName != null && functionArguments != null) { - val result = assistant.getToolRegistered(functionName, functionArguments) - val callId = toolCall.id - if (callId != null) { - callId to result - } else null - } else null + val result = assistant.getToolRegistered(functionName, functionArguments) + val callId = toolCall.id + return callId to result } catch (e: Throwable) { toolCall.id to Assistant.Companion.ToolOutput( @@ -195,22 +193,21 @@ class AssistantThread( } suspend fun getRun(runId: String): RunObject = - api.getRun(threadId, runId, configure = ::defaultConfig) + api.runs.getRun(threadId, runId, configure = ::defaultConfig) fun run(assistant: Assistant): Flow = createRunStream(assistant, CreateRunRequest(assistantId = assistant.assistantId)) suspend fun cancelRun(runId: String): RunObject = - api.cancelRun(threadId, runId, configure = ::defaultConfig) + api.runs.cancel.cancelRun(threadId, runId, configure = ::defaultConfig) suspend fun runSteps(runId: String): List = - api.listRunSteps(threadId, runId, configure = ::defaultConfig).data + api.runs.steps.listRunSteps(threadId, runId, configure = ::defaultConfig).data - private fun RunStepObjectStepDetails.toolCalls(): - List = + private fun RunStepObject.StepDetails.toolCalls(): List = when (val step = this) { - is RunStepObjectStepDetails.CaseRunStepDetailsMessageCreationObject -> emptyList() - is RunStepObjectStepDetails.CaseRunStepDetailsToolCallsObject -> step.value.toolCalls + is RunStepObject.StepDetails.CaseRunStepDetailsMessageCreationObject -> emptyList() + is RunStepObject.StepDetails.CaseRunStepDetailsToolCallsObject -> step.value.toolCalls } private suspend fun RunDelta.launchMetricsIfNecessary() { @@ -268,20 +265,21 @@ class AssistantThread( metadata: JsonObject? = null, metric: Metric = Metric.EMPTY, config: Config = Config(), - api: Assistants = OpenAI(config).assistants + api: Threads = OpenAI(config).threads ): AssistantThread = AssistantThread( threadId = api .createThread( - createThreadRequest = + body = CreateThreadRequest( messages = messages.map { CreateMessageRequest( - role = CreateMessageRequest.Role.user, - content = CreateMessageRequestContent.CaseString(it.content), - attachments = it.fileIds.map { MessageObjectAttachmentsInner(fileId = it) } + role = CreateMessageRequest.Role.User, + content = CreateMessageRequest.Content.CaseString(it.content), + attachments = + it.fileIds.map { CreateMessageRequest.Attachments(fileId = it) } ) }, metadata = metadata @@ -300,18 +298,18 @@ class AssistantThread( metadata: JsonObject? = null, metric: Metric = Metric.EMPTY, config: Config = Config(), - api: Assistants = OpenAI(config).assistants + api: Threads = OpenAI(config).threads ): AssistantThread = AssistantThread( api .createThread( - createThreadRequest = + body = CreateThreadRequest( messages = messages.map { CreateMessageRequest( - role = CreateMessageRequest.Role.user, - content = CreateMessageRequestContent.CaseString(it) + role = CreateMessageRequest.Role.User, + content = CreateMessageRequest.Content.CaseString(it) ) }, metadata = metadata @@ -330,7 +328,7 @@ class AssistantThread( metadata: JsonObject? = null, metric: Metric = Metric.EMPTY, config: Config = Config(), - api: Assistants = OpenAI(config).assistants + api: Threads = OpenAI(config).threads ): AssistantThread = AssistantThread( api @@ -348,7 +346,7 @@ class AssistantThread( request: CreateThreadRequest, metric: Metric = Metric.EMPTY, config: Config = Config(), - api: Assistants = OpenAI(config).assistants + api: Threads = OpenAI(config).threads ): AssistantThread = AssistantThread( api.createThread(request, configure = ::defaultConfig).id, @@ -361,10 +359,10 @@ class AssistantThread( request: CreateThreadAndRunRequest, metric: Metric = Metric.EMPTY, config: Config = Config(), - api: Assistants = OpenAI(config).assistants + api: Threads = OpenAI(config).threads ): AssistantThread = AssistantThread( - api.createThreadAndRun(request, configure = ::defaultConfig).id, + api.runs.createThreadAndRun(request, configure = ::defaultConfig).id, metric, config, api diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/RunDelta.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/RunDelta.kt index fec396e02..f38d369d6 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/RunDelta.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/RunDelta.kt @@ -1,8 +1,7 @@ package com.xebia.functional.xef.llm.assistants -import com.xebia.functional.openai.ServerSentEvent -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.Config +import com.xebia.functional.xef.openapi.* import kotlin.jvm.JvmInline import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -75,7 +74,7 @@ sealed interface RunDelta { @Serializable data class RunStepDeltaObjectInner( - @SerialName("step_details") val stepDetails: RunStepObjectStepDetails + @SerialName("step_details") val stepDetails: RunStepObject.StepDetails ) /** [RunDeltaEvent.thread_run_step_completed] */ @@ -237,19 +236,19 @@ sealed interface RunDelta { } is RunStepCompleted -> { when (val details = runStep.stepDetails) { - is RunStepObjectStepDetails.CaseRunStepDetailsMessageCreationObject -> + is RunStepObject.StepDetails.CaseRunStepDetailsMessageCreationObject -> println("Creating msg: ${details.value.messageCreation.messageId}") - is RunStepObjectStepDetails.CaseRunStepDetailsToolCallsObject -> + is RunStepObject.StepDetails.CaseRunStepDetailsToolCallsObject -> println( "Tool calls: ${details.value.toolCalls.map { when (it) { - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsCodeObject -> + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsCodeObject -> "Code: ${it.value.codeInterpreter.input}" - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsFunctionObject -> { + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsFunctionObject -> { val function = it.value.function "Function: ${function.name}(${function.arguments})" } - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsFileSearchObject -> { + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsFileSearchObject -> { val retrieval = it.value.fileSearch "Retrieval: $retrieval" } diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Tool.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Tool.kt index 5789961f0..331c20445 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Tool.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/assistants/Tool.kt @@ -1,7 +1,7 @@ package com.xebia.functional.xef.llm.assistants -import com.xebia.functional.openai.generated.model.FunctionObject import com.xebia.functional.xef.llm.chatFunction +import com.xebia.functional.xef.openapi.FunctionObject import kotlinx.serialization.KSerializer import kotlinx.serialization.serializer diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/Messages.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/Messages.kt index 1817d3f16..4d7db2902 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/Messages.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/Messages.kt @@ -1,6 +1,6 @@ package com.xebia.functional.xef.llm.models -import com.xebia.functional.openai.generated.model.CompletionUsage +import com.xebia.functional.xef.openapi.CompletionUsage data class MessagesWithUsage(val messages: List, val usage: MessagesUsage?) diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/OpenAIClient.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/OpenAIClient.kt index ba9b824f5..41e4114bf 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/OpenAIClient.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/llm/models/OpenAIClient.kt @@ -1,17 +1,17 @@ package com.xebia.functional.xef.llm.models -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel import com.xebia.functional.tokenizer.ModelType +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.CreateEmbeddingRequest -fun CreateChatCompletionRequestModel.modelType(forFunctions: Boolean = false): ModelType { +fun CreateChatCompletionRequest.Model.modelType(forFunctions: Boolean = false): ModelType { val stringValue = value val forFunctionsModel = ModelType.functionSpecific.find { forFunctions && it.name == stringValue } return forFunctionsModel ?: (ModelType.all.find { it.name == stringValue } ?: ModelType.TODO(stringValue)) } -fun CreateEmbeddingRequestModel.modelType(): ModelType { +fun CreateEmbeddingRequest.Model.modelType(): ModelType { val stringValue = value return ModelType.all.find { it.name == stringValue } ?: ModelType.TODO(stringValue) } diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/LogsMetric.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/LogsMetric.kt index c64c4d194..a151b1967 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/LogsMetric.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/LogsMetric.kt @@ -1,9 +1,9 @@ package com.xebia.functional.xef.metrics import arrow.atomic.AtomicInt -import com.xebia.functional.openai.generated.model.MessageObject -import com.xebia.functional.openai.generated.model.RunObject -import com.xebia.functional.openai.generated.model.RunStepObject +import com.xebia.functional.xef.openapi.MessageObject +import com.xebia.functional.xef.openapi.RunObject +import com.xebia.functional.xef.openapi.RunStepObject import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.contentAsString import io.github.oshai.kotlinlogging.KotlinLogging diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/Metric.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/Metric.kt index e7a1f47f0..3587920dd 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/Metric.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/metrics/Metric.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.metrics -import com.xebia.functional.openai.generated.model.MessageObject -import com.xebia.functional.openai.generated.model.RunObject -import com.xebia.functional.openai.generated.model.RunStepObject +import com.xebia.functional.xef.openapi.MessageObject +import com.xebia.functional.xef.openapi.RunObject +import com.xebia.functional.xef.openapi.RunStepObject import com.xebia.functional.xef.prompt.Prompt interface Metric { diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PlatformPromptBuilder.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PlatformPromptBuilder.kt index 0a80019a0..d39d64227 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PlatformPromptBuilder.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PlatformPromptBuilder.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.prompt -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.FunctionObject +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.FunctionObject import com.xebia.functional.xef.prompt.configuration.PromptConfiguration class PlatformPromptBuilder( - private val model: CreateChatCompletionRequestModel, + private val model: CreateChatCompletionRequest.Model, private val functions: List, private val configuration: PromptConfiguration ) : PromptBuilder { @@ -21,7 +21,7 @@ class PlatformPromptBuilder( companion object { fun create( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, functions: List, configuration: PromptConfiguration ): PlatformPromptBuilder = PlatformPromptBuilder(model, functions, configuration) diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/Prompt.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/Prompt.kt index 62ad2d355..1db9b6921 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/Prompt.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/Prompt.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.prompt -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.FunctionObject +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.FunctionObject import com.xebia.functional.xef.prompt.configuration.PromptConfiguration import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic @@ -14,19 +14,19 @@ import kotlin.jvm.JvmSynthetic data class Prompt @JvmOverloads constructor( - val model: CreateChatCompletionRequestModel, + val model: CreateChatCompletionRequest.Model, val messages: List, val functions: List = emptyList(), val configuration: PromptConfiguration = PromptConfiguration.DEFAULTS ) { constructor( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, value: String ) : this(model, listOf(PromptBuilder.user(value)), emptyList()) constructor( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, value: String, configuration: PromptConfiguration ) : this(model, listOf(PromptBuilder.user(value)), emptyList(), configuration) @@ -34,7 +34,7 @@ constructor( companion object { @JvmSynthetic operator fun invoke( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, functions: List = emptyList(), configuration: PromptConfiguration = PromptConfiguration.DEFAULTS, block: PlatformPromptBuilder.() -> Unit diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PromptBuilder.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PromptBuilder.kt index 3f90a791d..58869c0c0 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PromptBuilder.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/PromptBuilder.kt @@ -1,6 +1,7 @@ package com.xebia.functional.xef.prompt -import com.xebia.functional.openai.generated.model.* +import com.xebia.functional.xef.openapi.* +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage import com.xebia.functional.xef.prompt.configuration.PromptConfiguration import kotlin.jvm.JvmSynthetic @@ -26,12 +27,11 @@ interface PromptBuilder { ): ChatCompletionRequestMessage { val content = "${contentAsString()}\n${message.contentAsString()}" return when (completionRole()) { - ChatCompletionRole.Supported.system -> system(content) - ChatCompletionRole.Supported.user -> user(content) - ChatCompletionRole.Supported.assistant -> assistant(content) - ChatCompletionRole.Supported.tool -> error("Tool role is not supported") - ChatCompletionRole.Supported.function -> error("Function role is not supported") - is ChatCompletionRole.Custom -> error("Custom roles are not supported") + ChatCompletionRole.System -> system(content) + ChatCompletionRole.User -> user(content) + ChatCompletionRole.Assistant -> assistant(content) + ChatCompletionRole.Tool -> error("Tool role is not supported") + ChatCompletionRole.Function -> error("Function role is not supported") } } @@ -79,7 +79,7 @@ interface PromptBuilder { companion object { operator fun invoke( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, functions: List, configuration: PromptConfiguration ): PlatformPromptBuilder = PlatformPromptBuilder.create(model, functions, configuration) @@ -87,7 +87,7 @@ interface PromptBuilder { fun assistant(value: String): ChatCompletionRequestMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage( ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = value ) ) @@ -95,15 +95,15 @@ interface PromptBuilder { fun user(value: String): ChatCompletionRequestMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage( ChatCompletionRequestUserMessage( - role = ChatCompletionRequestUserMessage.Role.user, - content = ChatCompletionRequestUserMessageContent.CaseString(value) + role = ChatCompletionRequestUserMessage.Role.User, + content = ChatCompletionRequestUserMessage.Content.CaseString(value) ) ) fun tool(toolCallId: String, value: String): ChatCompletionRequestMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestToolMessage( ChatCompletionRequestToolMessage( - role = ChatCompletionRequestToolMessage.Role.tool, + role = ChatCompletionRequestToolMessage.Role.Tool, content = value, toolCallId = toolCallId ) @@ -112,7 +112,7 @@ interface PromptBuilder { fun system(value: String): ChatCompletionRequestMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestSystemMessage( ChatCompletionRequestSystemMessage( - role = ChatCompletionRequestSystemMessage.Role.system, + role = ChatCompletionRequestSystemMessage.Role.System, content = value ) ) @@ -120,21 +120,21 @@ interface PromptBuilder { fun image(url: String, text: String): ChatCompletionRequestMessage = ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage( ChatCompletionRequestUserMessage( - role = ChatCompletionRequestUserMessage.Role.user, + role = ChatCompletionRequestUserMessage.Role.User, content = - ChatCompletionRequestUserMessageContent.CaseChatCompletionRequestMessageContentParts( + ChatCompletionRequestUserMessage.Content.CaseChatCompletionRequestMessageContentParts( listOf( ChatCompletionRequestMessageContentPart .CaseChatCompletionRequestMessageContentPartImage( ChatCompletionRequestMessageContentPartImage( - type = ChatCompletionRequestMessageContentPartImage.Type.image_url, - imageUrl = ChatCompletionRequestMessageContentPartImageImageUrl(url) + type = ChatCompletionRequestMessageContentPartImage.Type.ImageUrl, + imageUrl = ChatCompletionRequestMessageContentPartImage.ImageUrl(url) ) ), ChatCompletionRequestMessageContentPart .CaseChatCompletionRequestMessageContentPartText( ChatCompletionRequestMessageContentPartText( - type = ChatCompletionRequestMessageContentPartText.Type.text, + type = ChatCompletionRequestMessageContentPartText.Type.Text, text = text ) ) @@ -149,8 +149,8 @@ fun ChatCompletionRequestMessage.contentAsString(): String = when (this) { is ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage -> when (val content = value.content) { - is ChatCompletionRequestUserMessageContent.CaseString -> content.value - is ChatCompletionRequestUserMessageContent.CaseChatCompletionRequestMessageContentParts -> + is ChatCompletionRequestUserMessage.Content.CaseString -> content.value + is ChatCompletionRequestUserMessage.Content.CaseChatCompletionRequestMessageContentParts -> content.value.joinToString { when (it) { is ChatCompletionRequestMessageContentPart.CaseChatCompletionRequestMessageContentPartImage -> diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/configuration/PromptConfiguration.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/configuration/PromptConfiguration.kt index 06d8b204c..2237b7fc3 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/configuration/PromptConfiguration.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/prompt/configuration/PromptConfiguration.kt @@ -1,7 +1,7 @@ package com.xebia.functional.xef.prompt.configuration -import com.xebia.functional.openai.generated.model.ChatCompletionRole import com.xebia.functional.xef.conversation.MessagePolicy +import com.xebia.functional.xef.openapi.ChatCompletionRole import kotlin.jvm.JvmField import kotlin.jvm.JvmOverloads import kotlinx.serialization.Serializable @@ -13,7 +13,7 @@ constructor( var maxDeserializationAttempts: Int = 3, var maxToolCallsPerRound: Int = 10000, var concurrentToolCallsPerRound: Int = 5, - var user: String = ChatCompletionRole.user.value, + var user: String = ChatCompletionRole.User.value, var temperature: Double = 0.4, var numberOfPredictions: Int = 1, var maxTokens: Int = 500, diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/CombinedVectorStore.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/CombinedVectorStore.kt index 4e10fb1de..97fb955e4 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/CombinedVectorStore.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/CombinedVectorStore.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.store -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.Embedding import com.xebia.functional.xef.llm.models.modelType +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.Embedding /** * A way of composing two [VectorStore] instances together, this class will **first search** [top], @@ -14,7 +14,7 @@ class CombinedVectorStore(private val top: VectorStore, private val bottom: Vect VectorStore by top { override suspend fun memories( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, conversationId: ConversationId, limitTokens: Int ): List { diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/LocalVectorStore.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/LocalVectorStore.kt index 67908be24..7e8755486 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/LocalVectorStore.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/LocalVectorStore.kt @@ -4,13 +4,10 @@ import arrow.atomic.Atomic import arrow.atomic.AtomicInt import arrow.atomic.getAndUpdate import arrow.atomic.update -import com.xebia.functional.openai.generated.api.Embeddings -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel -import com.xebia.functional.openai.generated.model.Embedding import com.xebia.functional.xef.llm.embedDocuments import com.xebia.functional.xef.llm.embedQuery import com.xebia.functional.xef.llm.models.modelType +import com.xebia.functional.xef.openapi.* import kotlin.math.sqrt private data class State( @@ -29,12 +26,12 @@ class LocalVectorStore private constructor( private val embeddings: Embeddings, private val state: AtomicState, - private val embeddingRequestModel: CreateEmbeddingRequestModel + private val embeddingRequestModel: CreateEmbeddingRequest.Model ) : VectorStore { constructor( embeddings: Embeddings, - embeddingRequestModel: CreateEmbeddingRequestModel = - CreateEmbeddingRequestModel.text_embedding_ada_002 + embeddingRequestModel: CreateEmbeddingRequest.Model = + CreateEmbeddingRequest.Model.TextEmbeddingAda002 ) : this(embeddings, Atomic(State.empty()), embeddingRequestModel) override val indexValue: AtomicInt = AtomicInt(0) @@ -63,7 +60,7 @@ private constructor( } override suspend fun memories( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, conversationId: ConversationId, limitTokens: Int ): List { diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/MemorizedMessage.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/MemorizedMessage.kt index 723690b45..aaf1ea1a4 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/MemorizedMessage.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/MemorizedMessage.kt @@ -1,13 +1,13 @@ package com.xebia.functional.xef.store -import com.xebia.functional.openai.generated.model.* +import com.xebia.functional.xef.openapi.* import com.xebia.functional.xef.prompt.completionRole sealed class MemorizedMessage { val role: ChatCompletionRole get() = when (this) { - is Request -> ChatCompletionRole.valueOf(message.completionRole().value) + is Request -> message.completionRole() is Response -> ChatCompletionRole.valueOf(message.role.name) } @@ -17,7 +17,7 @@ sealed class MemorizedMessage { is Response -> ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage( ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, // TODO: Find a new strategy to save the tool calls as content content = message.content ?: message.toolCalls?.firstOrNull()?.toString() ) @@ -31,32 +31,31 @@ sealed class MemorizedMessage { fun memorizedMessage(role: ChatCompletionRole, content: String): MemorizedMessage = when (role) { - ChatCompletionRole.Supported.system -> + ChatCompletionRole.System -> MemorizedMessage.Request( ChatCompletionRequestMessage.CaseChatCompletionRequestSystemMessage( ChatCompletionRequestSystemMessage( content = content, - role = ChatCompletionRequestSystemMessage.Role.system + role = ChatCompletionRequestSystemMessage.Role.System ) ) ) - ChatCompletionRole.Supported.user -> + ChatCompletionRole.User -> MemorizedMessage.Request( ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage( ChatCompletionRequestUserMessage( - content = ChatCompletionRequestUserMessageContent.CaseString(content), - role = ChatCompletionRequestUserMessage.Role.user + content = ChatCompletionRequestUserMessage.Content.CaseString(content), + role = ChatCompletionRequestUserMessage.Role.User ) ) ) - ChatCompletionRole.Supported.assistant -> + ChatCompletionRole.Assistant -> MemorizedMessage.Response( ChatCompletionResponseMessage( content = content, - role = ChatCompletionResponseMessage.Role.assistant + role = ChatCompletionResponseMessage.Role.Assistant ) ) - ChatCompletionRole.Supported.tool -> error("Tool messages are not supported") - ChatCompletionRole.Supported.function -> error("Function messages are not supported") - is ChatCompletionRole.Custom -> error("Custom messages are not supported") + ChatCompletionRole.Tool -> error("Tool messages are not supported") + ChatCompletionRole.Function -> error("Function messages are not supported") } diff --git a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/VectorStore.kt b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/VectorStore.kt index 65d13dce8..fb81a9508 100644 --- a/core/src/commonMain/kotlin/com/xebia/functional/xef/store/VectorStore.kt +++ b/core/src/commonMain/kotlin/com/xebia/functional/xef/store/VectorStore.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.store import arrow.atomic.AtomicInt -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.Embedding +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.Embedding import kotlin.jvm.JvmStatic interface VectorStore { @@ -16,7 +16,7 @@ interface VectorStore { suspend fun addMemories(memories: List) suspend fun memories( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, conversationId: ConversationId, limitTokens: Int ): List @@ -60,7 +60,7 @@ interface VectorStore { override suspend fun addMemories(memories: List) {} override suspend fun memories( - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, conversationId: ConversationId, limitTokens: Int ): List = emptyList() diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/conversation/ConversationSpec.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/conversation/ConversationSpec.kt index e659386df..b49e1c745 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/conversation/ConversationSpec.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/conversation/ConversationSpec.kt @@ -1,6 +1,5 @@ package com.xebia.functional.xef.conversation -import com.xebia.functional.openai.generated.model.* import com.xebia.functional.xef.data.TestChatApi import com.xebia.functional.xef.data.TestEmbeddings import com.xebia.functional.xef.llm.models.modelType @@ -8,6 +7,7 @@ import com.xebia.functional.xef.llm.promptMessage import com.xebia.functional.xef.llm.promptMessages import com.xebia.functional.xef.llm.tokensFromMessages import com.xebia.functional.xef.metrics.LogsMetric +import com.xebia.functional.xef.openapi.* import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.assistant import com.xebia.functional.xef.prompt.PromptBuilder.Companion.system @@ -28,7 +28,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_4 + val model = CreateChatCompletionRequest.Model.Gpt4 val scope = Conversation( @@ -66,7 +66,7 @@ class ConversationSpec : val vectorStore = scope.store val chatApi = TestChatApi(responses = messages) - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val totalTokens = model.modelType().tokensFromMessages(messages.flatMap(::chatCompletionRequestMessages)) @@ -102,7 +102,7 @@ class ConversationSpec : val vectorStore = scope.store val chatApi = TestChatApi(responses = messages) - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo_16k + val model = CreateChatCompletionRequest.Model.Gpt35Turbo16k val totalTokens = model.modelType().tokensFromMessages(messages.flatMap(::chatCompletionRequestMessages)) @@ -125,7 +125,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val scope = Conversation( @@ -145,7 +145,7 @@ class ConversationSpec : val firstResponse = chatApi.promptMessage(prompt = firstPrompt, scope = scope) val aiFirstMessage = ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = firstResponse ) @@ -154,7 +154,7 @@ class ConversationSpec : val secondResponse = chatApi.promptMessage(prompt = secondPrompt, scope = scope) val aiSecondMessage = ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = secondResponse ) @@ -167,7 +167,7 @@ class ConversationSpec : val thirdResponse = chatApi.promptMessage(prompt = thirdPrompt, scope = scope) val aiThirdMessage = ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = secondResponse ) @@ -190,7 +190,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -223,7 +223,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -252,7 +252,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -276,14 +276,14 @@ class ConversationSpec : println(messagesStored) - messagesStored.filter { it.content.role == ChatCompletionRole.system } shouldBe messagesStored + messagesStored.filter { it.content.role == ChatCompletionRole.System } shouldBe messagesStored } "when using MessagesToHistory.NOT_SYSTEM_MESSAGES policy, the scope's store shouldn't contains system messages" { val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -305,14 +305,14 @@ class ConversationSpec : val messagesStored = scope.store.memories(model, conversationId, Int.MAX_VALUE) - messagesStored.filter { it.content.role != ChatCompletionRole.system } shouldBe messagesStored + messagesStored.filter { it.content.role != ChatCompletionRole.System } shouldBe messagesStored } "when using MessagesToHistory.NONE policy, the scope's store shouldn't contains messages" { val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -334,7 +334,7 @@ class ConversationSpec : val messagesStored = scope.store.memories(model, conversationId, Int.MAX_VALUE).filter { - it.content.role == ChatCompletionRole.system + it.content.role == ChatCompletionRole.System } messagesStored.size shouldBe 0 @@ -344,7 +344,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -380,7 +380,7 @@ class ConversationSpec : val conversationId = ConversationId(UUID.generateUUID().toString()) val chatApi = TestChatApi() - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo val vectorStore = LocalVectorStore(TestEmbeddings()) @@ -417,13 +417,13 @@ private fun chatCompletionRequestMessages(it: Map.Entry) = listOf( ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage( ChatCompletionRequestUserMessage( - role = ChatCompletionRequestUserMessage.Role.user, - content = ChatCompletionRequestUserMessageContent.CaseString(it.key) + role = ChatCompletionRequestUserMessage.Role.User, + content = ChatCompletionRequestUserMessage.Content.CaseString(it.key) ) ), ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage( ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = it.value ) ), diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestChatApi.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestChatApi.kt index 99d675727..af0d01914 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestChatApi.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestChatApi.kt @@ -1,7 +1,7 @@ package com.xebia.functional.xef.data -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.* +import com.xebia.functional.xef.openapi.* +import com.xebia.functional.xef.openapi.Chat import com.xebia.functional.xef.prompt.contentAsString import io.ktor.client.request.* import kotlinx.coroutines.flow.Flow @@ -10,55 +10,56 @@ class TestChatApi(private val responses: Map = emptyMap()) : Cha var requests: MutableList = mutableListOf() - override suspend fun createChatCompletion( - createChatCompletionRequest: CreateChatCompletionRequest, - configure: HttpRequestBuilder.() -> Unit - ): CreateChatCompletionResponse { - requests.add(createChatCompletionRequest) - val response = - CreateChatCompletionResponse( - id = "fake-id", - `object` = CreateChatCompletionResponse.Object.chat_completion, - created = 0, - model = "fake-model", - choices = - listOf( - CreateChatCompletionResponseChoicesInner( - message = - ChatCompletionResponseMessage( - role = ChatCompletionResponseMessage.Role.assistant, - content = - responses[createChatCompletionRequest.messages.last().contentAsString()] - ?: "fake-content", - toolCalls = - listOf( - ChatCompletionMessageToolCall( - id = "fake-tool-id", - type = ChatCompletionMessageToolCall.Type.function, - function = - ChatCompletionMessageToolCallFunction( - "Answer", - """{ "bar": "fake-answer" }""" + override val completions: Chat.Completions = + object : Chat.Completions { + override suspend fun createChatCompletion( + body: CreateChatCompletionRequest, + configure: HttpRequestBuilder.() -> Unit + ): CreateChatCompletionResponse { + requests.add(body) + val response = + CreateChatCompletionResponse( + id = "fake-id", + `object` = CreateChatCompletionResponse.Object.ChatCompletion, + created = 0, + model = "fake-model", + choices = + listOf( + CreateChatCompletionResponse.Choices( + message = + ChatCompletionResponseMessage( + role = ChatCompletionResponseMessage.Role.Assistant, + content = responses[body.messages.last().contentAsString()] ?: "fake-content", + toolCalls = + listOf( + ChatCompletionMessageToolCall( + id = "fake-tool-id", + type = ChatCompletionMessageToolCall.Type.Function, + function = + ChatCompletionMessageToolCall.Function( + "Answer", + """{ "bar": "fake-answer" }""" + ) ) - ) - ) - ), - finishReason = CreateChatCompletionResponseChoicesInner.FinishReason.stop, - index = 0, - logprobs = null - ) - ), - usage = CompletionUsage(0, 0, 0) - ) - return response - } + ) + ), + finishReason = CreateChatCompletionResponse.Choices.FinishReason.Stop, + index = 0, + logprobs = null + ) + ), + usage = CompletionUsage(0, 0, 0) + ) + return response + } - override fun createChatCompletionStream( - createChatCompletionRequest: CreateChatCompletionRequest, - configure: HttpRequestBuilder.() -> Unit - ): Flow { - throw NotImplementedError("Not implemented") - } + override suspend fun createChatCompletionStream( + body: CreateChatCompletionRequest, + configure: HttpRequestBuilder.() -> Unit + ): Flow { + TODO("Not yet implemented") + } + } override fun close() {} } diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestEmbeddings.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestEmbeddings.kt index b607b7fc3..5dab226bb 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestEmbeddings.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/data/TestEmbeddings.kt @@ -1,9 +1,8 @@ package com.xebia.functional.xef.data -import com.xebia.functional.openai.generated.api.Embeddings -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequest -import com.xebia.functional.openai.generated.model.CreateEmbeddingResponse -import com.xebia.functional.openai.generated.model.CreateEmbeddingResponseUsage +import com.xebia.functional.xef.openapi.CreateEmbeddingRequest +import com.xebia.functional.xef.openapi.CreateEmbeddingResponse +import com.xebia.functional.xef.openapi.Embeddings import io.ktor.client.request.* class TestEmbeddings : Embeddings, AutoCloseable { @@ -19,8 +18,8 @@ class TestEmbeddings : Embeddings, AutoCloseable { CreateEmbeddingResponse( data = emptyList(), model = "", - `object` = CreateEmbeddingResponse.Object.list, - usage = CreateEmbeddingResponseUsage(0, 0) + `object` = CreateEmbeddingResponse.Object.List, + usage = CreateEmbeddingResponse.Usage(0, 0) ) return response } diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/prompt/PromptBuilderSpec.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/prompt/PromptBuilderSpec.kt index 45ee78648..1ba9144aa 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/prompt/PromptBuilderSpec.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/prompt/PromptBuilderSpec.kt @@ -1,6 +1,6 @@ package com.xebia.functional.xef.prompt -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import com.xebia.functional.xef.prompt.PromptBuilder.Companion.assistant import com.xebia.functional.xef.prompt.PromptBuilder.Companion.system import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user @@ -9,7 +9,7 @@ import io.kotest.matchers.shouldBe class PromptBuilderSpec : StringSpec({ - val model = CreateChatCompletionRequestModel.gpt_4 + val model = CreateChatCompletionRequest.Model.Gpt4 "buildPrompt should return the expected messages" { val messages = Prompt(model) { diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/CombinedVectorStoreSpec.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/CombinedVectorStoreSpec.kt index 54cca47ab..956cd8b63 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/CombinedVectorStoreSpec.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/CombinedVectorStoreSpec.kt @@ -1,15 +1,15 @@ package com.xebia.functional.xef.store -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.data.TestEmbeddings import com.xebia.functional.xef.llm.models.modelType import com.xebia.functional.xef.llm.tokensFromMessages +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe class CombinedVectorStoreSpec : StringSpec({ - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo "memories function should return all of messages combined in the right order" { val memoryData = MemoryData() diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/LocalVectorStoreSpec.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/LocalVectorStoreSpec.kt index abc6a22d4..48afc1ee7 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/LocalVectorStoreSpec.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/LocalVectorStoreSpec.kt @@ -1,15 +1,15 @@ package com.xebia.functional.xef.store -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.data.TestEmbeddings import com.xebia.functional.xef.llm.models.modelType import com.xebia.functional.xef.llm.tokensFromMessages +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe class LocalVectorStoreSpec : StringSpec({ - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo + val model = CreateChatCompletionRequest.Model.Gpt35Turbo "memories function should return all of messages in the right order when the limit is greater than the number of stored messages" { val localVectorStore = LocalVectorStore(TestEmbeddings()) diff --git a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/MemoryData.kt b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/MemoryData.kt index fd6ed7f02..59b200773 100644 --- a/core/src/commonTest/kotlin/com/xebia/functional/xef/store/MemoryData.kt +++ b/core/src/commonTest/kotlin/com/xebia/functional/xef/store/MemoryData.kt @@ -1,10 +1,9 @@ package com.xebia.functional.xef.store import arrow.atomic.AtomicInt -import com.xebia.functional.openai.generated.model.ChatCompletionRequestAssistantMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestUserMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestUserMessageContent +import com.xebia.functional.xef.openapi.ChatCompletionRequestAssistantMessage +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage +import com.xebia.functional.xef.openapi.ChatCompletionRequestUserMessage class MemoryData { val defaultConversationId = ConversationId("default-id") @@ -19,15 +18,15 @@ class MemoryData { (0 until n).flatMap { val m1 = ChatCompletionRequestUserMessage( - role = ChatCompletionRequestUserMessage.Role.user, + role = ChatCompletionRequestUserMessage.Role.User, content = - ChatCompletionRequestUserMessageContent.CaseString( + ChatCompletionRequestUserMessage.Content.CaseString( "Question $it${append?.let { ": $it" } ?: ""}" ) ) val m2 = ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = "Response $it${append?.let { ": $it" } ?: ""}" ) listOf( diff --git a/evaluator/build.gradle.kts b/evaluator/build.gradle.kts index 56ddda484..eb3355939 100644 --- a/evaluator/build.gradle.kts +++ b/evaluator/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { api(libs.kotlinx.serialization.json) detektPlugins(project(":detekt-rules")) implementation(projects.xefCore) - implementation(projects.xefOpenaiClient) } detekt { diff --git a/evaluator/src/main/kotlin/com/xebia/functional/xef/evaluator/SuiteBuilder.kt b/evaluator/src/main/kotlin/com/xebia/functional/xef/evaluator/SuiteBuilder.kt index 832a9d114..26e0755fb 100644 --- a/evaluator/src/main/kotlin/com/xebia/functional/xef/evaluator/SuiteBuilder.kt +++ b/evaluator/src/main/kotlin/com/xebia/functional/xef/evaluator/SuiteBuilder.kt @@ -1,10 +1,10 @@ package com.xebia.functional.xef.evaluator -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.AI import com.xebia.functional.xef.AIConfig import com.xebia.functional.xef.PromptClassifier import com.xebia.functional.xef.evaluator.models.* +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -12,7 +12,7 @@ import kotlinx.serialization.serializer class SuiteBuilder( private val description: String, - private val model: CreateChatCompletionRequestModel + private val model: CreateChatCompletionRequest.Model ) { private val items = mutableListOf() @@ -28,7 +28,7 @@ class SuiteBuilder( data class SuiteSpec( val description: String, val items: List, - val model: CreateChatCompletionRequestModel + val model: CreateChatCompletionRequest.Model ) { suspend inline fun evaluate(success: List): SuiteResults where @@ -62,7 +62,7 @@ data class SuiteSpec( suspend operator fun invoke( description: String, - model: CreateChatCompletionRequestModel, + model: CreateChatCompletionRequest.Model, block: suspend SuiteBuilder.() -> Unit ): SuiteSpec = SuiteBuilder(description, model).apply { block() }.build() diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/assistants/AssistantStreaming.kt b/examples/src/main/kotlin/com/xebia/functional/xef/assistants/AssistantStreaming.kt index 8fecf412b..98a4c369e 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/assistants/AssistantStreaming.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/assistants/AssistantStreaming.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.assistants -import com.xebia.functional.openai.generated.model.CreateRunRequest import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.llm.assistants.Assistant import com.xebia.functional.xef.llm.assistants.AssistantThread import com.xebia.functional.xef.llm.assistants.Tool import com.xebia.functional.xef.metrics.Metric +import com.xebia.functional.xef.openapi.CreateRunRequest suspend fun main() { @@ -19,7 +19,7 @@ suspend fun main() { toolsConfig = listOf(Tool.toolOf(SumTool())) ) val config = Config(org = null) - val api = OpenAI(config = config, logRequests = false).assistants + val api = OpenAI(config = config, logRequests = false).threads val thread = AssistantThread(api = api, metric = metric) println("Welcome to the Math tutor, ask me anything about math:") val userInput = "What is 1+1, explain all the steps and tools you used to solve it." diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/assistants/DSL.kt b/examples/src/main/kotlin/com/xebia/functional/xef/assistants/DSL.kt index 04c13c081..836188580 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/assistants/DSL.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/assistants/DSL.kt @@ -5,6 +5,7 @@ import com.xebia.functional.xef.llm.assistants.Assistant import com.xebia.functional.xef.llm.assistants.AssistantThread import com.xebia.functional.xef.llm.assistants.RunDelta import com.xebia.functional.xef.llm.assistants.Tool +import com.xebia.functional.xef.openapi.OpenAI import kotlinx.serialization.Serializable @Serializable data class SumInput(val left: Int, val right: Int) @@ -49,7 +50,7 @@ suspend fun main() { assistantId = "asst_UxczzpJkysC0l424ood87DAk", toolsConfig = listOf(Tool.toolOf(SumTool())) ) - val thread = AssistantThread(api = OpenAI(logRequests = false).assistants, metric = metric) + val thread = AssistantThread(api = OpenAI(logRequests = false).threads, metric = metric) println("Welcome to the Math tutor, ask me anything about math:") while (true) { println() diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/conversation/conversations/Animal.kt b/examples/src/main/kotlin/com/xebia/functional/xef/conversation/conversations/Animal.kt index a43f15e62..02516286b 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/conversation/conversations/Animal.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/conversation/conversations/Animal.kt @@ -1,6 +1,5 @@ package com.xebia.functional.xef.conversation.conversations -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.AI import com.xebia.functional.xef.AIConfig import com.xebia.functional.xef.OpenAI @@ -8,6 +7,8 @@ import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.conversation.MessagesFromHistory import com.xebia.functional.xef.conversation.MessagesToHistory import com.xebia.functional.xef.llm.promptMessage +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.OpenAI import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.system import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user @@ -36,7 +37,7 @@ suspend fun main() { val configNoneFromConversation = PromptConfiguration { messagePolicy { addMessagesFromConversation = MessagesFromHistory.NONE } } - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo_16k_0613 + val model = CreateChatCompletionRequest.Model.Gpt35Turbo16k0613 val config = AIConfig(model = model, openAI = openAI, conversation = this@Conversation) val animal: Animal = AI( diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/audio/SimpleSpeech.kt b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/audio/SimpleSpeech.kt index 5a942d26b..5cec92a9b 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/audio/SimpleSpeech.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/audio/SimpleSpeech.kt @@ -1,13 +1,13 @@ package com.xebia.functional.xef.dsl.audio import com.sipgate.mp3wav.Converter -import com.xebia.functional.openai.UploadFile -import com.xebia.functional.openai.generated.model.CreateSpeechRequest -import com.xebia.functional.openai.generated.model.CreateSpeechRequestModel -import com.xebia.functional.openai.generated.model.CreateTranscriptionRequestModel import com.xebia.functional.xef.AI import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI +import com.xebia.functional.xef.openapi.CreateSpeechRequest +import com.xebia.functional.xef.openapi.CreateTranscriptionRequest +import com.xebia.functional.xef.openapi.CreateTranslationRequest +import com.xebia.functional.xef.openapi.UploadFile import io.ktor.client.statement.* import io.ktor.utils.io.jvm.javaio.* import java.io.File @@ -28,11 +28,11 @@ suspend fun main() { .trimIndent() ) val channel = - audio.createSpeech( + audio.speech.createSpeech( CreateSpeechRequest( - model = CreateSpeechRequestModel.tts_1, + model = CreateSpeechRequest.Model.Tts1, input = modelResponse, - voice = CreateSpeechRequest.Voice.nova + voice = CreateSpeechRequest.Voice.Nova ) ) val wavConverter = Converter(channel.bodyAsChannel().toInputStream()) @@ -51,19 +51,23 @@ suspend fun main() { bodyBuilder = { file.readBytes().forEach { writeByte(it) } } ) val transcription = - audio.createTranscription( - model = CreateTranscriptionRequestModel.whisper_1, - prompt = "Translate to spanish", - file = uploadFile, - language = "es", + audio.transcriptions.createTranscription( + CreateTranscriptionRequest( + file = uploadFile, + model = CreateTranscriptionRequest.Model.Whisper1, + prompt = "Translate to spanish", + language = "es" + ) ) println("transcription in `es`:") println(transcription) val translation = - audio.createTranslation( - model = CreateTranscriptionRequestModel.whisper_1, - prompt = "Translate", - file = uploadFile, + audio.translations.createTranslation( + CreateTranslationRequest( + file = uploadFile, + model = CreateTranslationRequest.Model.Whisper1, + prompt = "Translate" + ) ) println("translation to english:") println(translation) diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/Classes.kt b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/Classes.kt index 4842c3844..2cd82eac7 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/Classes.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/Classes.kt @@ -1,11 +1,11 @@ package com.xebia.functional.xef.dsl.chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.AI import com.xebia.functional.xef.conversation.Description import com.xebia.functional.xef.conversation.MessagePolicy import com.xebia.functional.xef.conversation.MessagesFromHistory import com.xebia.functional.xef.conversation.MessagesToHistory +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.assistant import com.xebia.functional.xef.prompt.PromptBuilder.Companion.system @@ -26,7 +26,7 @@ data class Book( ) suspend fun books(topic: String): Books { - val model = CreateChatCompletionRequestModel.gpt_4_turbo_preview + val model = CreateChatCompletionRequest.Model.Gpt4TurboPreview val myCustomPrompt = Prompt( diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/ParallelToolCallsAnonymous.kt b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/ParallelToolCallsAnonymous.kt index 2cfdc904b..3a8027ebf 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/ParallelToolCallsAnonymous.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/chat/ParallelToolCallsAnonymous.kt @@ -1,11 +1,11 @@ package com.xebia.functional.xef.dsl.chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.AI import com.xebia.functional.xef.AIConfig import com.xebia.functional.xef.AIEvent import com.xebia.functional.xef.Tool import com.xebia.functional.xef.conversation.Description +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import kotlinx.coroutines.flow.Flow suspend fun ballLocationInfoFromLastCupTriedImpl(input: Int): String { @@ -27,7 +27,7 @@ suspend fun main() { prompt = "Where is the ball? use the available tools to find out.", config = AIConfig( - model = CreateChatCompletionRequestModel.gpt_3_5_turbo, + model = CreateChatCompletionRequest.Model.Gpt35Turbo, tools = listOf( Tool.suspend( diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/images/ImagesExamples.kt b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/images/ImagesExamples.kt index 01b71aa19..50c88ab74 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/dsl/images/ImagesExamples.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/dsl/images/ImagesExamples.kt @@ -1,18 +1,20 @@ package com.xebia.functional.xef.dsl.images -import com.xebia.functional.openai.generated.model.CreateImageRequest import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.llm.asInputProvider +import com.xebia.functional.xef.openapi.CreateImageRequest +import com.xebia.functional.xef.openapi.CreateImageVariationRequest +import com.xebia.functional.xef.openapi.OpenAI suspend fun main() { val openAI = OpenAI(logRequests = false) val ai = openAI.images - val image = - ai.createImage( - createImageRequest = CreateImageRequest(prompt = "Event horizon in a black hole") - ) + val image = ai.generations.createImage(CreateImageRequest("Event horizon in a black hole")) val generatedImage = image.data.first() println("Image: $generatedImage") - val variant = ai.createImageVariation(generatedImage.asInputProvider()) + val variant = + ai.variations.createImageVariation( + CreateImageVariationRequest(generatedImage.asInputProvider()) + ) println("Variant: $variant") } diff --git a/examples/src/main/kotlin/com/xebia/functional/xef/evaluator/TestExample.kt b/examples/src/main/kotlin/com/xebia/functional/xef/evaluator/TestExample.kt index e51f70487..67a40d588 100644 --- a/examples/src/main/kotlin/com/xebia/functional/xef/evaluator/TestExample.kt +++ b/examples/src/main/kotlin/com/xebia/functional/xef/evaluator/TestExample.kt @@ -1,7 +1,6 @@ package com.xebia.functional.xef.evaluator import arrow.continuations.SuspendApp -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.evaluator.metrics.AnswerAccuracy @@ -9,6 +8,8 @@ import com.xebia.functional.xef.evaluator.models.ModelsPricing import com.xebia.functional.xef.evaluator.models.OutputDescription import com.xebia.functional.xef.evaluator.models.OutputResponse import com.xebia.functional.xef.llm.promptMessageAndUsage +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest +import com.xebia.functional.xef.openapi.OpenAI import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user import java.io.File @@ -17,13 +18,13 @@ object TestExample { @JvmStatic fun main(args: Array) = SuspendApp { - val model = CreateChatCompletionRequestModel.gpt_3_5_turbo_16k + val model = CreateChatCompletionRequest.Model.Gpt35Turbo16k val chat = OpenAI(logRequests = false).chat val spec = SuiteSpec( description = "Check GTP3.5 and fake outputs", - model = CreateChatCompletionRequestModel.gpt_4_turbo_preview + model = CreateChatCompletionRequest.Model.Gpt4TurboPreview ) { val gpt35Description = OutputDescription("Using GPT3.5") val fakeOutputs = OutputDescription("Fake outputs with errors") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f2d20937..12b6707de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ progressbar = "0.10.0" jmf = "2.1.1e" mp3-wav-converter = "1.0.4" yamlkt="0.13.0" - +openapi-kt="0.0.6" [libraries] arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } @@ -143,3 +143,4 @@ semver-gradle = { id="com.javiersc.semver", version.ref="semverGradle" } resources = { id="com.goncalossilva.resources", version.ref="resources-kmp" } detekt = { id="io.gitlab.arturbosch.detekt", version.ref="detekt"} node-gradle = { id = "com.github.node-gradle.node", version.ref = "node-gradle" } +openapi-kt = { id= "io.github.nomisrev.openapi-kt-plugin", version.ref = "openapi-kt" } diff --git a/integrations/gcp/src/commonMain/kotlin/com/xebia/functional/xef/gcp/GcpClient.kt b/integrations/gcp/src/commonMain/kotlin/com/xebia/functional/xef/gcp/GcpClient.kt index b668193fd..bc3d343e4 100644 --- a/integrations/gcp/src/commonMain/kotlin/com/xebia/functional/xef/gcp/GcpClient.kt +++ b/integrations/gcp/src/commonMain/kotlin/com/xebia/functional/xef/gcp/GcpClient.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.gcp -import com.xebia.functional.openai.apis.EmbeddingsApi -import com.xebia.functional.openai.models.CreateEmbeddingRequest -import com.xebia.functional.openai.models.ext.embedding.create.CreateEmbeddingRequestInput +import com.xebia.functional.xef.openapi.apis.EmbeddingsApi +import com.xebia.functional.xef.openapi.models.CreateEmbeddingRequest +import com.xebia.functional.xef.openapi.models.ext.embedding.create.CreateEmbeddingRequestInput import com.xebia.functional.xef.AIError import com.xebia.functional.xef.conversation.AutoClose import com.xebia.functional.xef.conversation.autoClose diff --git a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryAssistantState.kt b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryAssistantState.kt index 616c17a75..059c94f77 100644 --- a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryAssistantState.kt +++ b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryAssistantState.kt @@ -1,6 +1,6 @@ package com.xebia.functional.xef.opentelemetry -import com.xebia.functional.openai.generated.model.* +import com.xebia.functional.xef.openapi.* import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.Tracer @@ -146,23 +146,23 @@ class OpenTelemetryAssistantState(private val tracer: Tracer) { span.setAttribute("openai.assistant.runStep.id", id) span.setAttribute("openai.assistant.status", status.name) when (val detail = stepDetails) { - is RunStepObjectStepDetails.CaseRunStepDetailsMessageCreationObject -> { + is RunStepObject.StepDetails.CaseRunStepDetailsMessageCreationObject -> { span.setAttribute( "openai.assistant.messageCreation.id", detail.value.messageCreation.messageId ) } - is RunStepObjectStepDetails.CaseRunStepDetailsToolCallsObject -> { + is RunStepObject.StepDetails.CaseRunStepDetailsToolCallsObject -> { detail.value.toolCalls.forEachIndexed { index, toolCall -> when (toolCall) { - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsCodeObject -> { + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsCodeObject -> { span.setAttribute("openai.assistant.toolCalls.$index.type", toolCall.value.type.name) span.setAttribute( "openai.assistant.toolCalls.$index.function.name", "code_interpreter" ) } - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsFunctionObject -> { + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsFunctionObject -> { span.setAttribute("openai.assistant.toolCalls.$index.type", toolCall.value.type.name) span.setAttribute( "openai.assistant.toolCalls.$index.function.name", @@ -173,7 +173,7 @@ class OpenTelemetryAssistantState(private val tracer: Tracer) { toolCall.value.function.arguments ?: "" ) } - is RunStepDetailsToolCallsObjectToolCallsInner.CaseRunStepDetailsToolCallsFileSearchObject -> { + is RunStepDetailsToolCallsObject.ToolCalls.CaseRunStepDetailsToolCallsFileSearchObject -> { span.setAttribute("openai.assistant.toolCalls.$index.type", toolCall.value.type.name) span.setAttribute("openai.assistant.toolCalls.$index.function.name", "retrieval") } @@ -192,13 +192,13 @@ class OpenTelemetryAssistantState(private val tracer: Tracer) { span.setAttribute("openai.assistant.message.id", id) status?.let { span.setAttribute("openai.assistant.message.status", it.name) } when (val inner = content.firstOrNull()) { - is MessageObjectContentInner.CaseMessageContentImageFileObject -> { + is MessageObject.Content.CaseMessageContentImageFileObject -> { span.setAttribute("openai.assistant.message.content", inner.value.imageFile.fileId) } - is MessageObjectContentInner.CaseMessageContentTextObject -> { + is MessageObject.Content.CaseMessageContentTextObject -> { span.setAttribute("openai.assistant.message.content", inner.value.text.value) } - is MessageObjectContentInner.CaseMessageContentImageUrlObject -> + is MessageObject.Content.CaseMessageContentImageUrlObject -> span.setAttribute("openai.assistant.message.content", inner.value.imageUrl.url) null -> {} } diff --git a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt index 34f859818..a5dc754f9 100644 --- a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt +++ b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt @@ -1,9 +1,9 @@ package com.xebia.functional.xef.opentelemetry -import com.xebia.functional.openai.generated.model.MessageObject -import com.xebia.functional.openai.generated.model.RunObject -import com.xebia.functional.openai.generated.model.RunStepObject import com.xebia.functional.xef.metrics.Metric +import com.xebia.functional.xef.openapi.MessageObject +import com.xebia.functional.xef.openapi.RunObject +import com.xebia.functional.xef.openapi.RunStepObject import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.contentAsString import io.opentelemetry.api.trace.* diff --git a/integrations/postgresql/src/main/kotlin/com/xebia/functional/xef/store/PostgreSQLVectorStore.kt b/integrations/postgresql/src/main/kotlin/com/xebia/functional/xef/store/PostgreSQLVectorStore.kt index cd32df067..1e82b8287 100644 --- a/integrations/postgresql/src/main/kotlin/com/xebia/functional/xef/store/PostgreSQLVectorStore.kt +++ b/integrations/postgresql/src/main/kotlin/com/xebia/functional/xef/store/PostgreSQLVectorStore.kt @@ -1,16 +1,12 @@ package com.xebia.functional.xef.store import arrow.atomic.AtomicInt -import com.xebia.functional.openai.generated.api.Embeddings -import com.xebia.functional.openai.generated.model.ChatCompletionRole -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel -import com.xebia.functional.openai.generated.model.Embedding import com.xebia.functional.xef.llm.embedDocuments import com.xebia.functional.xef.llm.embedQuery import com.xebia.functional.xef.llm.models.modelType import com.xebia.functional.xef.prompt.contentAsString import com.xebia.functional.xef.store.postgresql.* +import com.xebia.functional.xef.openapi.* import kotlinx.uuid.UUID import kotlinx.uuid.generateUUID import javax.sql.DataSource @@ -22,7 +18,7 @@ class PGVectorStore( private val collectionName: String, private val distanceStrategy: PGDistanceStrategy, private val preDeleteCollection: Boolean, - private val embeddingRequestModel: CreateEmbeddingRequestModel, + private val embeddingRequestModel: CreateEmbeddingRequest.Model, private val chunkSize: Int = 400 ) : VectorStore { @@ -38,7 +34,7 @@ class PGVectorStore( update(addNewMemory) { bind(UUID.generateUUID().toString()) bind(memory.conversationId.value) - bind(memory.content.role.value.lowercase()) + bind(memory.content.role.name) bind(memory.content.asRequestMessage().contentAsString()) bind(memory.index) } @@ -46,7 +42,7 @@ class PGVectorStore( } } - override suspend fun memories(model: CreateChatCompletionRequestModel, conversationId: ConversationId, limitTokens: Int): List = + override suspend fun memories(model: CreateChatCompletionRequest.Model, conversationId: ConversationId, limitTokens: Int): List = getMemoryByConversationId(conversationId).reduceByLimitToken(model.modelType(), limitTokens).reversed() private fun JDBCSyntax.getCollection(collectionName: String): PGCollection = @@ -153,7 +149,7 @@ class PGVectorStore( val index = int() Memory( conversationId = ConversationId(cId), - content = memorizedMessage(ChatCompletionRole.valueOf(role.lowercase()), content), + content = memorizedMessage(ChatCompletionRole.valueOf(role), content), index = index ) } diff --git a/integrations/postgresql/src/test/kotlin/xef/MemoryData.kt b/integrations/postgresql/src/test/kotlin/xef/MemoryData.kt index 00e7a76b9..541c42772 100644 --- a/integrations/postgresql/src/test/kotlin/xef/MemoryData.kt +++ b/integrations/postgresql/src/test/kotlin/xef/MemoryData.kt @@ -1,10 +1,9 @@ package xef import arrow.atomic.AtomicInt -import com.xebia.functional.openai.generated.model.ChatCompletionRequestAssistantMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestUserMessage -import com.xebia.functional.openai.generated.model.ChatCompletionRequestUserMessageContent +import com.xebia.functional.xef.openapi.ChatCompletionRequestAssistantMessage +import com.xebia.functional.xef.openapi.ChatCompletionRequestMessage +import com.xebia.functional.xef.openapi.ChatCompletionRequestUserMessage import com.xebia.functional.xef.store.ConversationId import com.xebia.functional.xef.store.MemorizedMessage import com.xebia.functional.xef.store.Memory @@ -22,13 +21,13 @@ class MemoryData { (0 until n).flatMap { val m1 = ChatCompletionRequestMessage.CaseChatCompletionRequestUserMessage( ChatCompletionRequestUserMessage( - role = ChatCompletionRequestUserMessage.Role.user, - content = ChatCompletionRequestUserMessageContent.CaseString("Question $it${append?.let { ": $it" } ?: ""}") + role = ChatCompletionRequestUserMessage.Role.User, + content = ChatCompletionRequestUserMessage.Content.CaseString("Question $it${append?.let { ": $it" } ?: ""}") ) ) val m2 = ChatCompletionRequestMessage.CaseChatCompletionRequestAssistantMessage( ChatCompletionRequestAssistantMessage( - role = ChatCompletionRequestAssistantMessage.Role.assistant, + role = ChatCompletionRequestAssistantMessage.Role.Assistant, content = "Answer $it${append?.let { ": $it" } ?: ""}" ) ) diff --git a/integrations/postgresql/src/test/kotlin/xef/PGVectorStoreSpec.kt b/integrations/postgresql/src/test/kotlin/xef/PGVectorStoreSpec.kt index c7e5ab83b..f916e26de 100644 --- a/integrations/postgresql/src/test/kotlin/xef/PGVectorStoreSpec.kt +++ b/integrations/postgresql/src/test/kotlin/xef/PGVectorStoreSpec.kt @@ -1,13 +1,11 @@ package xef -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel -import com.xebia.functional.openai.generated.model.Embedding import com.xebia.functional.xef.store.PGVectorStore import com.xebia.functional.xef.store.migrations.runDatabaseMigrations import com.xebia.functional.xef.store.postgresql.PGDistanceStrategy import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource +import com.xebia.functional.xef.openapi.* import io.kotest.core.Tuple3 import io.kotest.core.extensions.install import io.kotest.core.spec.style.StringSpec @@ -39,7 +37,7 @@ class PGVectorStoreSpec : ) ) - val embeddingsRequestModel = CreateEmbeddingRequestModel.text_embedding_ada_002 + val embeddingsRequestModel = CreateEmbeddingRequest.Model.TextEmbeddingAda002 fun StringSpecScope.pg() = PGVectorStore( @@ -87,7 +85,7 @@ class PGVectorStoreSpec : "similaritySearchByVector should return both documents" { pg().addTexts(listOf("bar", "foo")) - pg().similaritySearchByVector(Embedding(0, listOf(4.0, 5.0, 6.0), Embedding.Object.embedding), 2) shouldBe + pg().similaritySearchByVector(Embedding(0, listOf(4.0, 5.0, 6.0), Embedding.Object.Embedding), 2) shouldBe listOf("bar", "foo") } @@ -102,14 +100,14 @@ class PGVectorStoreSpec : "similaritySearchByVector should return document" { pg().similaritySearchByVector( - Embedding(0, listOf(1.0, 2.0, 3.0), Embedding.Object.embedding), + Embedding(0, listOf(1.0, 2.0, 3.0), Embedding.Object.Embedding), 1 ) shouldBe listOf("foo") } "the added memories sorted by index should be obtained in the same order" { val memoryData = MemoryData() - val model = CreateChatCompletionRequestModel.gpt_4 + val model = CreateChatCompletionRequest.Model.Gpt4 val memories = memoryData.generateRandomMessages(10) pg().addMemories(memories) memories.map { Tuple3(it.index, it.conversationId, it.content.asRequestMessage()) } shouldBe diff --git a/integrations/postgresql/src/test/kotlin/xef/TestEmbeddings.kt b/integrations/postgresql/src/test/kotlin/xef/TestEmbeddings.kt index a56584375..3b614e49d 100644 --- a/integrations/postgresql/src/test/kotlin/xef/TestEmbeddings.kt +++ b/integrations/postgresql/src/test/kotlin/xef/TestEmbeddings.kt @@ -1,7 +1,6 @@ package xef -import com.xebia.functional.openai.generated.api.Embeddings -import com.xebia.functional.openai.generated.model.* +import com.xebia.functional.xef.openapi.* import io.ktor.client.request.* class TestEmbeddings : Embeddings, AutoCloseable { @@ -14,25 +13,25 @@ class TestEmbeddings : Embeddings, AutoCloseable { ): CreateEmbeddingResponse { requests.add(createEmbeddingRequest) val maybeTextInput = when(val input = createEmbeddingRequest.input) { - is CreateEmbeddingRequestInput.CaseStrings -> input.value.firstOrNull() - is CreateEmbeddingRequestInput.CaseString -> input.value + is CreateEmbeddingRequest.Input.CaseStrings -> input.value.firstOrNull() + is CreateEmbeddingRequest.Input.CaseString -> input.value else -> null } val data = when(maybeTextInput) { "fooz" -> listOf( - Embedding(0, listOf(7.0, 8.0, 9.0), Embedding.Object.embedding), - Embedding(1, listOf(7.5, 8.5, 9.5), Embedding.Object.embedding) + Embedding(0, listOf(7.0, 8.0, 9.0), Embedding.Object.Embedding), + Embedding(1, listOf(7.5, 8.5, 9.5), Embedding.Object.Embedding) ) - "foo" -> listOf(Embedding(0, listOf(1.0, 2.0, 3.0), Embedding.Object.embedding)) - "bar" -> listOf(Embedding(0, listOf(4.0, 5.0, 6.0), Embedding.Object.embedding)) + "foo" -> listOf(Embedding(0, listOf(1.0, 2.0, 3.0), Embedding.Object.Embedding)) + "bar" -> listOf(Embedding(0, listOf(4.0, 5.0, 6.0), Embedding.Object.Embedding)) "baz" -> listOf() else -> listOf() } return CreateEmbeddingResponse( data = data, model = "test-model", - `object` = CreateEmbeddingResponse.Object.list, - usage = CreateEmbeddingResponseUsage(0, 0) + `object` = CreateEmbeddingResponse.Object.List, + usage = CreateEmbeddingResponse.Usage(0, 0) ) } diff --git a/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/SQL.kt b/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/SQL.kt index 6aceaf222..869ec65ff 100644 --- a/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/SQL.kt +++ b/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/SQL.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.sql -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel +import com.xebia.functional.xef.openapi.Chat import com.xebia.functional.xef.Tool import com.xebia.functional.xef.conversation.AiDsl import com.xebia.functional.xef.conversation.Conversation import com.xebia.functional.xef.conversation.Description import com.xebia.functional.xef.llm.* +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest import com.xebia.functional.xef.prompt.Prompt import com.xebia.functional.xef.prompt.PromptBuilder.Companion.system import com.xebia.functional.xef.prompt.PromptBuilder.Companion.user @@ -44,7 +44,7 @@ interface SQL { suspend fun Conversation.promptQuery(prompt: String, tableNames: List, context: String?): AnswerResponse } -class SQLImpl(private val chatApi: Chat, private val model: CreateChatCompletionRequestModel, private val db: Database) : SQL { +class SQLImpl(private val chatApi: Chat, private val model: CreateChatCompletionRequest.Model, private val db: Database) : SQL { private val logger = KotlinLogging.logger {} override suspend fun Conversation.promptQuery( diff --git a/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/jdbc/JdbcConfig.kt b/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/jdbc/JdbcConfig.kt index c740a3dc5..ac5e56193 100644 --- a/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/jdbc/JdbcConfig.kt +++ b/integrations/sql/src/main/kotlin/com/xebia/functional/xef/sql/jdbc/JdbcConfig.kt @@ -1,7 +1,7 @@ package com.xebia.functional.xef.sql.jdbc -import com.xebia.functional.openai.generated.api.Chat -import com.xebia.functional.openai.generated.model.CreateChatCompletionRequestModel +import com.xebia.functional.xef.openapi.Chat +import com.xebia.functional.xef.openapi.CreateChatCompletionRequest class JdbcConfig( val vendor: String, @@ -11,7 +11,7 @@ class JdbcConfig( val port: Int, val database: String, val chatApi: Chat, - val model: CreateChatCompletionRequestModel + val model: CreateChatCompletionRequest.Model ) { fun toJDBCUrl(): String = "jdbc:$vendor://$host:$port/$database" } diff --git a/openai-client/client/.openapi-generator-ignore b/openai-client/client/.openapi-generator-ignore deleted file mode 100644 index 4b8839f00..000000000 --- a/openai-client/client/.openapi-generator-ignore +++ /dev/null @@ -1,15 +0,0 @@ -.gitignore -build.gradle.kts -settings.gradle.kts -gradle/** -gradlew -gradlew.bat -README.md -docs/** - -build.gradle -settings.gradle - -**/src/test/ -/**/org/openapitools/client/infrastructure/ -**/proguard-rules.pro diff --git a/openai-client/client/build.gradle.kts b/openai-client/client/build.gradle.kts deleted file mode 100644 index 7beb5a2a1..000000000 --- a/openai-client/client/build.gradle.kts +++ /dev/null @@ -1,138 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -import org.jetbrains.dokka.gradle.DokkaTask - -repositories { mavenCentral() } - -plugins { - base - alias(libs.plugins.kotlin.multiplatform) - alias(libs.plugins.kotest.multiplatform) - alias(libs.plugins.kotlinx.serialization) - alias(libs.plugins.spotless) - alias(libs.plugins.dokka) - alias(libs.plugins.arrow.gradle.publish) - alias(libs.plugins.semver.gradle) - alias(libs.plugins.detekt) -} - -dependencies { detektPlugins(project(":detekt-rules")) } - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - toolchain { languageVersion = JavaLanguageVersion.of(11) } -} - -kotlin { - sourceSets.commonMain { - kotlin.srcDir(project.file("build/generated/OpenAI/src/commonMain/kotlin")) - } -} - -// Automatically run the generator when importing -tasks.maybeCreate("prepareKotlinIdeaImport") - .dependsOn(":xef-openai-client-generator:openaiClientGenerate") - -detekt { - toolVersion = "1.23.1" - source.setFrom("src/commonMain/kotlin", "src/jvmMain/kotlin") - config.setFrom("../../config/detekt/detekt.yml") - autoCorrect = true -} - -kotlin { - jvm() - js(IR) { - browser() - nodejs() - // TODO support wasm, etc - } - linuxX64() - macosX64() - macosArm64() - mingwX64() - // iOS, Android, etc? - sourceSets { - all { languageSettings.optIn("kotlin.ExperimentalStdlibApi") } - val commonMain by getting { - dependencies { - api(projects.xefTokenizer) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.client.serialization) - implementation(libs.ktor.client.logging) - implementation(libs.klogging) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(libs.kotest.property) - implementation(libs.kotest.assertions) - } - } - val jvmMain by getting { - dependencies { - api(libs.ktor.client.cio) - } - } - val jsMain by getting { dependencies { api(libs.ktor.client.js) } } - val jvmTest by getting { dependencies { implementation(libs.kotest.junit5) } } - val linuxX64Main by getting { dependencies { api(libs.ktor.client.cio) } } - val macosX64Main by getting { dependencies { api(libs.ktor.client.cio) } } - val macosArm64Main by getting { dependencies { api(libs.ktor.client.cio) } } - val mingwX64Main by getting { dependencies { api(libs.ktor.client.winhttp) } } - } -} - -spotless { - kotlin { - target("**/*.kt") - ktfmt().googleStyle().configure { it.setRemoveUnusedImport(true) } - } -} - -tasks { - withType().configureEach { - dependsOn(":detekt-rules:assemble") - autoCorrect = true - } - named("detektJvmMain") { - dependsOn(":detekt-rules:assemble") - getByName("build").dependsOn(this) - } - named("detekt") { - dependsOn(":detekt-rules:assemble") - getByName("build").dependsOn(this) - } - withType().configureEach { - maxParallelForks = Runtime.getRuntime().availableProcessors() - useJUnitPlatform() - testLogging { - setExceptionFormat("full") - setEvents(listOf("passed", "skipped", "failed", "standardOut", "standardError")) - } - } - withType().configureEach { - kotlin.sourceSets.forEach { kotlinSourceSet -> - dokkaSourceSets.named(kotlinSourceSet.name) { - perPackageOption { - matchingRegex.set(".*\\.internal.*") - suppress.set(true) - } - skipDeprecated.set(true) - reportUndocumented.set(false) - val baseUrl = checkNotNull(project.properties["pom.smc.url"]?.toString()) - kotlinSourceSet.kotlin.srcDirs.filter { it.exists() }.forEach { srcDir -> - sourceLink { - localDirectory.set(srcDir) - remoteUrl.set(uri("$baseUrl/blob/main/${srcDir.relativeTo(rootProject.rootDir)}").toURL()) - remoteLineSuffix.set("#L") - } - } - } - } - } -} - -tasks.withType { dependsOn(tasks.withType()) } diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/AssistantEvent.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/AssistantEvent.kt deleted file mode 100644 index 64f9e266d..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/AssistantEvent.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.xebia.functional.openai - -import kotlinx.serialization.Serializable - -// TODO write proper AssistantEvent -// According to: -// https://platform.openai.com/docs/api-reference/assistants-streaming/message-delta-object -@Serializable data class AssistantEvent(val todo: String) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/Config.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/Config.kt deleted file mode 100644 index 3efb3c9b9..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/Config.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.xebia.functional.openai - -import kotlinx.serialization.json.Json - -data class Config( - val baseUrl: String, - val token: String, - val org: String?, - val json: Json, - val streamingPrefix: String, - val streamingDelimiter: String -) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/FormBuilderOps.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/FormBuilderOps.kt deleted file mode 100644 index 13fed8050..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/FormBuilderOps.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.xebia.functional.openai - -import io.ktor.client.request.forms.* -import io.ktor.http.* -import io.ktor.util.* -import io.ktor.utils.io.core.* -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.serializerOrNull - -@OptIn(InternalAPI::class) -fun FormBuilder.appendGen(key: String, value: T, headers: Headers = Headers.Empty): Unit { - when (value) { - is String -> append(key, value, headers) - is Number -> append(key, value, headers) - is Boolean -> append(key, value, headers) - is ByteArray -> append(key, value, headers) - is ByteReadPacket -> append(key, value, headers) - is InputProvider -> append(key, value, headers) - is ChannelProvider -> append(key, value, headers) - is UploadFile -> appendUploadedFile(key, value) - is Enum<*> -> append(key, serialNameOrEnumValue(value), headers) - else -> append(key, value, headers) - } -} - -internal fun FormBuilder.appendUploadedFile(key: String, file: UploadFile) { - append( - key = key, - filename = file.filename, - contentType = file.contentType ?: ContentType.Application.OctetStream, - size = file.size, - bodyBuilder = file.bodyBuilder - ) -} - -@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) -fun > serialNameOrEnumValue(v: Enum): String = - v::class.serializerOrNull()?.descriptor?.getElementName(v.ordinal) ?: v.toString() diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/OneOfOps.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/OneOfOps.kt deleted file mode 100644 index 0cd3ed386..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/OneOfOps.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.xebia.functional.openai - -import kotlin.reflect.KClass -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.JsonElement - -class OneOfSerializationException( - val payload: JsonElement, - val errors: Map, SerializationException>, - override val message: String = - """ - Failed to deserialize Json: $payload. - Errors: ${ - errors.entries.joinToString(separator = "\n") { (type, error) -> - "$type - failed to deserialize: ${error.stackTraceToString()}" - } - } - """ - .trimIndent() -) : SerializationException(message) - -/** - * OpenAI makes a lot of use of oneOf types (sum types, or unions types), but it **never** relies on - * a discriminator field to differentiate between the types. - * - * Typically, what OpenAI does is attach a common field like `type` (a single value enum). I.e. - * `MessageObjectContentInner` has a type field with `image` or `text`. Depending on the `type` - * property, the other properties will be different. - * - * Due to the use of these fields, it **seems** there are no overlapping objects in the schema. So - * to deserialize these types, we can try to deserialize each type and return the first one that - * succeeds. In the case **all** fail, we throw [OneOfSerializationException] which includes all the - * attempted types with their errors. - * - * This method relies on 'peeking', which is not possible in KotlinX Serialization. So to achieve - * peeking, we first deserialize the raw Json to JsonElement, which safely consumes the buffer. And - * then we can attempt to deserialize the JsonElement to the desired type, without breaking the - * internal parser buffer. - */ -internal fun attemptDeserialize( - json: JsonElement, - vararg block: Pair, (json: JsonElement) -> A> -): A { - val errors = linkedMapOf, SerializationException>() - block.forEach { (kclass, f) -> - try { - return f(json) - } catch (e: SerializationException) { - errors[kclass] = e - } - } - throw OneOfSerializationException(json, errors) -} diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/ServerSentEvent.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/ServerSentEvent.kt deleted file mode 100644 index a3427178b..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/ServerSentEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.xebia.functional.openai - -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - -@Serializable -data class ServerSentEvent( - val event: String? = null, - val data: JsonElement? = null, -) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/StreamingOps.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/StreamingOps.kt deleted file mode 100644 index 6727ce030..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/StreamingOps.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.xebia.functional.openai - -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.bodyAsChannel -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.readUTF8Line -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.serializer - -// ServerSentEvent, INTERNAL TO THIS MODULE -// RunDelta | CreateChatCompletionStreamResponse -internal suspend inline fun FlowCollector.streamEvents( - response: HttpResponse, - json: Json, - prefix: String, - end: String -) { - val channel: ByteReadChannel = response.bodyAsChannel() - var nextEvent: String? = null - while (!channel.isClosedForRead) { - val line = channel.readUTF8Line() ?: continue - - // break when we reach the end of the stream - if (line.startsWith(end)) { - break - } - - // if the line is an event like "event: thread.created" we want to ensure - // A is of type ServerSentEvent and we skip the line treating next `prefix` as a JsonObject - // otherwise we treat the line as a json object if it starts with the prefix - // and emit the value - - // if the line is an event like "event: thread.created" we want to ensure - // A is of type ServerSentEvent and we skip the line treating next `prefix` as a JsonObject - if (line.startsWith("event:")) { - nextEvent = line.removePrefix("event:").trim() - continue - } - // otherwise we treat the line as a json object if it starts with the prefix - else if (line.startsWith(prefix) && nextEvent == null) { - val data = line.removePrefix(prefix).trim() - val value: A = json.decodeFromString(serializer(), data) - emit(value) - } - // emit the value for the next event - if (nextEvent != null) { - val data = line.removePrefix(prefix).trim() - if (data.isNotBlank()) { - val eventData = json.decodeFromString(JsonObject.serializer(), data) - val value: A = ServerSentEvent(event = nextEvent, data = eventData) as A - emit(value) - } - } - } - - // serverevent as A -} diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/UploadFile.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/UploadFile.kt deleted file mode 100644 index d97aaac84..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/UploadFile.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.xebia.functional.openai - -import io.ktor.http.* -import io.ktor.utils.io.core.* - -data class UploadFile( - val filename: String, - val contentType: ContentType? = null, - val size: Long? = null, - val bodyBuilder: BytePacketBuilder.() -> Unit -) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt deleted file mode 100644 index c55a8e3aa..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerError.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.xebia.functional.openai.errors - -class ResponseSerializerError(message: String, cause: Throwable? = null) : - Exception(message, cause) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt deleted file mode 100644 index 4fafce148..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/errors/ResponseSerializerErrors.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.xebia.functional.openai.errors - -import io.ktor.client.call.* -import io.ktor.client.statement.* -import io.ktor.http.content.* - -suspend inline fun HttpResponse.serializeOrThrowWithResponseInfo(): A = - try { - this.body() ?: throw ResponseSerializerError("Response body is null") - } catch (e: Exception) { - val requestBody = - when (val content = this.request.content) { - is OutgoingContent.ByteArrayContent -> content.bytes().decodeToString() - is OutgoingContent.NoContent -> "NoContent" - is OutgoingContent.ProtocolUpgrade -> "ProtocolUpgrade" - is OutgoingContent.ReadChannelContent -> "ReadChannelContent" - is OutgoingContent.WriteChannelContent -> "WriteChannelContent" - else -> "UnknownContent" - } - throw ResponseSerializerError( - """ - |Failed to serialize response body to ${A::class.simpleName} - |Request URL: ${this.request.url} - |Request Method: ${this.request.method} - |Request Body: $requestBody - |Response Status: ${this.status} - |Response Headers: ${this.headers} - |Response Body: ${this.bodyAsText()} - """ - .trimMargin(), - e - ) - } diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateAssistantRequestToolResourcesFileSearch.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateAssistantRequestToolResourcesFileSearch.kt deleted file mode 100644 index ad9af6f21..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateAssistantRequestToolResourcesFileSearch.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.xebia.functional.openai.generated.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CreateAssistantRequestToolResourcesFileSearch( - @SerialName(value = "vector_store_ids") val vectorStoreIds: List -) diff --git a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateThreadRequestToolResourcesFileSearch.kt b/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateThreadRequestToolResourcesFileSearch.kt deleted file mode 100644 index cf348e230..000000000 --- a/openai-client/client/src/commonMain/kotlin/com/xebia/functional/openai/generated/model/CreateThreadRequestToolResourcesFileSearch.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.xebia.functional.openai.generated.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CreateThreadRequestToolResourcesFileSearch( - @SerialName(value = "vector_store_ids") val vectorStoreIds: List -) diff --git a/openai-client/generator/.openapi-generator-ignore b/openai-client/generator/.openapi-generator-ignore deleted file mode 100644 index dc1bce6c5..000000000 --- a/openai-client/generator/.openapi-generator-ignore +++ /dev/null @@ -1,9 +0,0 @@ -**/docs/** -**/README.md -**/test/** -**/org/openapitools/** -**/proguard-rules.pro -**/build.gradle -**/settings.gradle -**/CreateAssistantRequestToolResourcesFileSearch.kt -**/CreateThreadRequestToolResourcesFileSearch.kt diff --git a/openai-client/generator/README.md b/openai-client/generator/README.md deleted file mode 100644 index 97e0f75c0..000000000 --- a/openai-client/generator/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# OpenAI Client - -Autogenerated client for OpenAI using the OpenAPI specification and [OpenAPI Generator](https://openapi-generator.tech/) - -## Download API Specification - -Requirements: -* File `openai-client/generator/config/openai-api-commit` with the desired commit of the OpenAI API - -Running the task `downloadOpenAIAPI` will read that commit and download the specification into `openai-client/generator/config/openai-api.yaml` - -```shell -./gradlew downloadOpenAIAPI -``` - -## Generate code - -Requirements: -* File `openai-client/generator/config/openai-api.yaml` with the API specification - -Running the task `openaiClientGenerate` will: -* Generate the code with OpenAI Generator using the configuration -* Run the task `spotlessApply` - -```shell -./gradlew openaiClientGenerate -``` - -## Further Customization - -We choose for a semi-manual approach to generate the code. -With a preference for a more controlled way of generating the code, so prefer custom template over workarounds. -This generator add support for `oneOf`, and potentially more in the future. - -Since we focus on KMP, we use KotlinX Serialization and Ktor. -So by being opinionated, we can heavily simply the templates. Which is great for maintainability. - -- [KMMGenerator](src/main/kotlin/com/openai/generator/KMMGenerator.kt) that generates the code. -- config/*.mustache files with the templates for the code generation. - - api.mustache: invoked for each API endpoint. Generates interface, and smart-constructor. - - openai.mustache: the OpenAI interface that exposes all the API endpoints, with fluent syntax. - - model.mustache: invoked for each model. Delegate to the model templates (data, enum, oneOf). - - data.mustache: generates the data class for the model (including nested models). - - enum.mustache: generates the enum class for the model (including nested models). - - oneOf.mustache: generates the sealed class for the model (including nested models). - -### Debugging - -``` -./gradlew openaiClientGenerate -PdebugModels=true > model.log -``` -``` -./gradlew openaiClientGenerate -PdebugOperations=true > operations.log -``` - -You can only debug one of the two properties at a time. _operations_ are used to debug the API endpoints, _models_ are used to debug the models. -It's best to dump the output in a file, such that you can inspect them in a more controlled way, -but they should be included in git! - -### Mustache - -[Mustache(5)](https://mustache.github.io) is being used as template engine, -with [Mustache Java](https://github.com/spullara/mustache.java). - -Quick reference: - - triple `{`: {{{var_name}}} is (HTML escaped) - - double `{{`: {{var_name}} is plain value - - {{#var_name}} ... {{/var_name}} is a section, drill down into model, for each, value present, etc. - - {{^var_name}} ... {{/var_name}} is a negative section, if var_name is empty, or false, etc. - - {{! comment }} is a comment - - {{> partial }} is a partial, include another template - - {{& var_name }} is a triple mustache without escaping - - {{#-first}} ... {{/-first}} only execute for first/last element in an array - - {{^-last}} ... {{/-last}} only execute for first/last element in an array - - {{#lambda.name}} ... {{/lambda.name}} is a lambda, a function that can be called with a parameter - - Following are available: (uppercase, snakecase, titlecase, kebabcase, camelcase, pascalcase, forwardslash, backslash, doublequote, indented, indented_8, indented_12, indented_16, escapeDollar) - - And our custom ones: (oneOfName, capitalised, unquote, jsname) diff --git a/openai-client/generator/build.gradle.kts b/openai-client/generator/build.gradle.kts deleted file mode 100644 index 8e3398049..000000000 --- a/openai-client/generator/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - java - alias(libs.plugins.spotless) -} - -dependencies { - implementation("org.openapitools:openapi-generator-cli:7.4.0") -} - -tasks.test { - useJUnitPlatform() -} - -task("downloadOpenAIAPI", JavaExec::class) { - group = "GenerateTasks" - mainClass = "ai.xef.openai.generator.DownloadOpenAIAPI" - classpath = sourceSets["main"].runtimeClasspath -} - -task("openaiClientGenerate", JavaExec::class) { - group = "GenerateTasks" - mainClass = "org.openapitools.codegen.OpenAPIGenerator" - val command = (listOf( - "generate", - "-i", - "config/openai-api.yaml", - "-g", - "ai.xef.openai.generator.KMMGeneratorConfig", - "-o", - "../client/build/generated/OpenAI/", - "--skip-validate-spec" - ) + if (project.hasProperty("debugModels") && !project.hasProperty("debugOperations")) { - listOf("--global-property", "debugModels=true") - } else if(!project.hasProperty("debugModels") && project.hasProperty("debugOperations")) { - listOf("--global-property", "debugOperations=true") - } else emptyList()) - - args = command - classpath = sourceSets["main"].runtimeClasspath -} diff --git a/openai-client/generator/config/api.mustache b/openai-client/generator/config/api.mustache deleted file mode 100644 index 0c18d55ef..000000000 --- a/openai-client/generator/config/api.mustache +++ /dev/null @@ -1,194 +0,0 @@ -{{>licenseInfo}} -package {{apiPackage}} - -{{#imports}}import {{import}} -{{/imports}} -import com.xebia.functional.openai.Config -import com.xebia.functional.openai.UploadFile -import com.xebia.functional.openai.appendGen -import com.xebia.functional.openai.generated.api.{{classname}}.* -import com.xebia.functional.openai.streamEvents -import com.xebia.functional.openai.errors.serializeOrThrowWithResponseInfo -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.plugins.timeout -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.client.request.accept -import io.ktor.client.request.header -import io.ktor.client.request.forms.formData -import io.ktor.client.request.parameter -import io.ktor.client.request.prepareRequest -import io.ktor.client.request.request -import io.ktor.client.request.setBody -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.HttpStatement -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.http.contentType -import io.ktor.http.path -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlin.time.Duration.Companion.seconds -import kotlin.time.DurationUnit - -{{#operations}} -/** - * - */ -interface {{classname}} { - {{#operation}} - {{#allParams}} - {{#isEnum}} - - /** - * enum for parameter {{paramName}} - */ - @Serializable - {{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{enumName}}{{operationIdCamelCase}}(val value: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}kotlin.String{{/isContainer}}) { - {{^enumUnknownDefaultCase}} - {{#allowableValues}}{{#enumVars}} - @SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) - {{&name}}({{{value}}}){{^-last}},{{/-last}} - {{/enumVars}}{{/allowableValues}} - {{/enumUnknownDefaultCase}} - {{#enumUnknownDefaultCase}} - {{#allowableValues}}{{#enumVars}}{{^-last}} - @SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) - {{&name}}({{{value}}}), - {{/-last}}{{/enumVars}}{{/allowableValues}} - {{/enumUnknownDefaultCase}} - } - - {{/isEnum}} - {{/allParams}} - - /** - * {{summary}} - * {{notes}} - {{#allParams}} * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}} - {{/allParams}} * @param configure optional configuration for the request, allows overriding the default configuration. - * @return {{{returnType}}}{{^returnType}}void{{/returnType}} - */ - suspend fun {{operationId}}({{#allParams}}{{{paramName}}}: {{#isEnum}}{{# isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}, {{/allParams}}configure: HttpRequestBuilder.() -> Unit = {}): {{#returnProperty}}{{#isFile}}HttpResponse{{/isFile}}{{^isFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isFile}}{{/returnProperty}} - - {{#vendorExtensions.x-streaming}} - /** - * Streaming variant: {{summary}} - * By default, the client is modified to timeout after 60 seconds. Which is overridable by the [configure]. - * {{notes}} - {{#allParams}} * @param {{{paramName}}} {{description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{#required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}{{/required}} - {{/allParams}} * @param configure optional configuration for the request, allows overriding the default configuration. - * @return [Flow]<[{{{vendorExtensions.x-streaming-return}}}]> - */ - fun {{operationId}}Stream({{#allParams}}{{{paramName}}}: {{#isEnum}}{{# isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}, {{/allParams}}configure: HttpRequestBuilder.() -> Unit = {}): Flow<{{{vendorExtensions.x-streaming-return}}}> - {{/vendorExtensions.x-streaming}} - {{/operation}} -} -{{/operations}} - -{{#operations}} -fun {{classname}}(client: HttpClient, config: Config): {{apiPackage}}.{{classname}} = object : {{apiPackage}}.{{classname}} { - {{#operation}} - override suspend fun {{operationId}}({{#allParams}}{{{paramName}}}: {{#isEnum}}{{# isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}},{{/allParams}} configure: HttpRequestBuilder.() -> Unit): {{#returnProperty}}{{#isFile}}HttpResponse{{/isFile}}{{^isFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isFile}}{{/returnProperty}} = - client.request { - configure() - method = HttpMethod.{{#lambda.capitalised}}{{httpMethod}}{{/lambda.capitalised}} - {{#headerParams}} - header("{{baseName}}", {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}}) - {{/headerParams}} - contentType(ContentType.Application.Json) - {{#queryParams}} - parameter("{{baseName}}", {{#isContainer}}toMultiValue(this, "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}) - {{/queryParams}} - url { path("{{#lambda.dropslash}}{{path}}{{/lambda.dropslash}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}"${{{paramName}}}"{{/isContainer}}){{/pathParams}}) } - setBody({{#hasBodyParam}}{{#bodyParam}}{{#isArray}}{{operationIdCamelCase}}Request({{{paramName}}}{{^isList}}.asList(){{/isList}}){{/isArray}}{{^isArray}}{{#isMap}}{{operationIdCamelCase}}Request({{{paramName}}}){{/isMap}}{{^isMap}}{{{paramName}}}{{/isMap}}{{/isArray}}{{/bodyParam}}{{/hasBodyParam}} - {{^hasBodyParam}} - {{#hasFormParams}} - {{#isMultipart}} - formData { - {{#formParams}} - {{#isArray}} - {{{paramName}}}?.onEach { appendGen("{{{baseName}}}[]", it) } - {{/isArray}} - {{^isArray}} - {{{paramName}}}?.apply { appendGen("{{{baseName}}}", {{{paramName}}}) } - {{/isArray}} - {{/formParams}} - } - {{/isMultipart}} - {{^isMultipart}} - ParametersBuilder().also { - {{#formParams}} - {{{paramName}}}?.apply { it.append("{{{baseName}}}", {{{paramName}}}.toString()) } - {{/formParams}} - }.build() - {{/isMultipart}} - {{/hasFormParams}} - {{^hasFormParams}} - io.ktor.client.utils.EmptyContent - {{/hasFormParams}} - {{/hasBodyParam}}) - }{{#returnProperty}}{{^isFile}}.serializeOrThrowWithResponseInfo(){{/isFile}}{{/returnProperty}} - - {{#vendorExtensions.x-streaming}} - override fun {{operationId}}Stream({{#allParams}}{{{paramName}}}: {{#isEnum}}{{# isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{#isEnum}}{{enumName}}{{operationIdCamelCase}}.{{enumDefaultValue}}{{/isEnum}}{{^isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isNumber}}{{#isNumber}}{{{defaultValue}}}.toDouble(){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}, {{/allParams}}configure: HttpRequestBuilder.() -> Unit): Flow<{{{vendorExtensions.x-streaming-return}}}> = flow { - client.prepareRequest { - timeout { - requestTimeoutMillis = 60.seconds.toLong(DurationUnit.MILLISECONDS) - socketTimeoutMillis = 60.seconds.toLong(DurationUnit.MILLISECONDS) - } - configure() - method = HttpMethod.{{#lambda.capitalised}}{{httpMethod}}{{/lambda.capitalised}} - accept(ContentType.Text.EventStream) - header(HttpHeaders.CacheControl, "no-cache") - header(HttpHeaders.Connection, "keep-alive") - {{#headerParams}} - header("{{baseName}}", {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}}) - {{/headerParams}} - contentType(ContentType.Application.Json) - {{#queryParams}} - parameter("{{baseName}}", {{#isContainer}}toMultiValue(this, "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{{paramName}}}"){{/isContainer}}) - {{/queryParams}} - url { path("{{#lambda.dropslash}}{{path}}{{/lambda.dropslash}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}"${{{paramName}}}"{{/isContainer}}){{/pathParams}}) } - {{#hasBodyParam}} - val element = Json.encodeToJsonElement({{#lambda.serializer}}{{#bodyParam}}{{{baseName}}}{{/bodyParam}}{{/lambda.serializer}}, {{#bodyParam}}{{{paramName}}}{{/bodyParam}}) - val jsObject = JsonObject(element.jsonObject + Pair("stream", JsonPrimitive(true))) - setBody(jsObject) - {{/hasBodyParam}} - {{^hasBodyParam}} - {{#hasFormParams}} - {{#isMultipart}} - formData { - {{#formParams}} - {{#isArray}} - {{{paramName}}}?.onEach { appendGen("{{{baseName}}}[]", it) } - {{/isArray}} - {{^isArray}} - {{{paramName}}}?.apply { appendGen("{{{baseName}}}", {{{paramName}}}) } - {{/isArray}} - {{/formParams}} - } - {{/isMultipart}} - {{^isMultipart}} - ParametersBuilder().also { - {{#formParams}} - {{{paramName}}}?.apply { it.append("{{{baseName}}}", {{{paramName}}}.toString()) } - {{/formParams}} - }.build() - {{/isMultipart}} - {{/hasFormParams}} - {{/hasBodyParam}} - }.execute { streamEvents(it, config.json, config.streamingPrefix, config.streamingDelimiter) } - } - {{/vendorExtensions.x-streaming}} -{{/operation}} -} -{{/operations}} diff --git a/openai-client/generator/config/data_class.mustache b/openai-client/generator/config/data_class.mustache deleted file mode 100644 index 6ccb7d95f..000000000 --- a/openai-client/generator/config/data_class.mustache +++ /dev/null @@ -1,68 +0,0 @@ -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerialName -import kotlinx.serialization.Contextual -import kotlin.js.JsName -import kotlinx.serialization.json.* -{{#enumUnknownDefaultCase}} -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -{{/enumUnknownDefaultCase}} -{{#hasEnums}} -{{/hasEnums}} - -/** -* {{{description}}} -* -{{#allVars}} - * @param {{{name}}} {{{description}}} -{{/allVars}} -*/ -{{^discriminator}}@Serializable{{/discriminator}} -{{#isDeprecated}}@Deprecated(message = "This schema is deprecated."){{/isDeprecated}} -{{>additionalModelTypeAnnotations}} -{{#nonPublicApi}}internal {{/nonPublicApi}}{{#discriminator}}interface{{/discriminator}}{{^discriminator}}data class{{/discriminator}} {{classname}}{{^discriminator}} ( -{{#allVars}} - {{#required}}{{>data_class_req_var}}{{/required}}{{^required}}{{>data_class_opt_var}}{{/required}}{{^-last}},{{/-last}} -{{/allVars}} -){{/discriminator}}{{#parent}} : {{{parent}}}{{#isMap}}(){{/isMap}}{{#isArray}}(){{/isArray}}{{/parent}} { -{{#discriminator}}{{#vars}}{{#required}} - {{>interface_req_var}}{{/required}}{{^required}} - {{>interface_opt_var}}{{/required}}{{/vars}}{{/discriminator}} -{{#hasEnums}} - {{#vars}} - {{#isEnum}} - /** - * {{{description}}} - * - * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} - */ - @Serializable{{#enumUnknownDefaultCase}}(with = {{classname}}Serializer::class){{/enumUnknownDefaultCase}} - {{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{{nameInCamelCase}}}(val value: {{dataType}}) { - {{#allowableValues}} - {{#enumVars}} - @SerialName(value = {{#lambda.doublequote}}{{{value}}}{{/lambda.doublequote}}) {{#lambda.jsname}}{{&name}}{{/lambda.jsname}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} - {{/enumVars}} - {{/allowableValues}} - }{{#kotlinx_serialization}}{{#enumUnknownDefaultCase}} - - @Serializer(forClass = {{{nameInCamelCase}}}::class) - internal object {{nameInCamelCase}}Serializer : KSerializer<{{nameInCamelCase}}> { - override val descriptor = {{{dataType}}}.serializer().descriptor - - override fun deserialize(decoder: Decoder): {{nameInCamelCase}} { - val value = decoder.decodeSerializableValue({{{dataType}}}.serializer()) - return {{nameInCamelCase}}.values().firstOrNull { it.value == value } - ?: {{nameInCamelCase}}.{{#allowableValues}}{{#enumVars}}{{#-last}}{{&name}}{{/-last}}{{/enumVars}}{{/allowableValues}} - } - - override fun serialize(encoder: Encoder, value: {{nameInCamelCase}}) { - encoder.encodeSerializableValue({{{dataType}}}.serializer(), value.value) - } - }{{/enumUnknownDefaultCase}}{{/kotlinx_serialization}} - {{/isEnum}} - {{/vars}} -{{/hasEnums}} -} \ No newline at end of file diff --git a/openai-client/generator/config/data_class_opt_var.mustache b/openai-client/generator/config/data_class_opt_var.mustache deleted file mode 100644 index d59b528d1..000000000 --- a/openai-client/generator/config/data_class_opt_var.mustache +++ /dev/null @@ -1,7 +0,0 @@ -{{#description}} - /* {{{.}}} */ -{{/description}} - {{#deprecated}} - @Deprecated(message = "This property is deprecated.") - {{/deprecated}} - @SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}{{#uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}Set{{/uniqueItems}}{{^uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInCamelCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInCamelCase}}}{{/isArray}}{{/isEnum}}? = {{^defaultValue}}null{{/defaultValue}}{{#defaultValue}}{{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}({{{defaultValue}}}).toDouble(){{/isNumber}}{{/defaultValue}} \ No newline at end of file diff --git a/openai-client/generator/config/data_class_req_var.mustache b/openai-client/generator/config/data_class_req_var.mustache deleted file mode 100644 index 17b800584..000000000 --- a/openai-client/generator/config/data_class_req_var.mustache +++ /dev/null @@ -1,7 +0,0 @@ -{{#description}} - /* {{{.}}} */ -{{/description}} - {{#deprecated}} - @Deprecated(message = "This property is deprecated.") - {{/deprecated}} - @SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{#isInherited}}override {{/isInherited}}val {{{name}}}: {{#isArray}}{{#isList}}{{#uniqueItems}}kotlin.collections.Set{{/uniqueItems}}{{^uniqueItems}}kotlin.collections.List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}@Contextual {{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInCamelCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInCamelCase}}}{{/isArray}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}({{{defaultValue}}}).toDouble(){{/isNumber}}{{/defaultValue}} \ No newline at end of file diff --git a/openai-client/generator/config/enum_class.mustache b/openai-client/generator/config/enum_class.mustache deleted file mode 100644 index 535b97aa5..000000000 --- a/openai-client/generator/config/enum_class.mustache +++ /dev/null @@ -1,53 +0,0 @@ -import kotlinx.serialization.* -import kotlinx.serialization.builtins.* -import kotlinx.serialization.encoding.* -import kotlin.jvm.JvmStatic -{{#allowableValues}}{{#enumVars}}import com.xebia.functional.openai.generated.model.{{classname}}.Supported.{{#lambda.unquote}}{{&value}}{{/lambda.unquote}} -{{/enumVars}}{{/allowableValues}} - -/** - * {{{description}}} - */ -// We define a serializer for the parent sum type, and then use it to serialize the child types -@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") -@Serializable(with = {{classname}}Serializer::class) -sealed interface {{classname}} { - val value: {{{dataType}}} - - @Serializable(with = {{classname}}Serializer::class) - enum class Supported(override val value: {{{dataType}}}) : {{classname}} { - {{#allowableValues}}{{#enumVars}}@SerialName(value = {{{value}}}) {{#lambda.unquote}}{{&value}}{{/lambda.unquote}}({{#isArray}}{{#isList}}listOf({{{value}}})){{/isList}}{{^isList}}arrayOf({{{value}}})){{/isList}}{{/isArray}}{{^isArray}}{{#isString}}{{#lambda.doublequote}}{{{value}}}{{/lambda.doublequote}}{{/isString}}{{^isString}}{{{value}}}{{/isString}}{{/isArray}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} - - override fun toString(): kotlin.String = value{{^isString}}.toString(){{/isString}} - } - - @Serializable(with = {{classname}}Serializer::class) - data class Custom(override val value: kotlin.String) : {{classname}} - - companion object { - @JvmStatic - fun valueOf(value: {{{dataType}}}): {{classname}} = - values().firstOrNull { it.value == value } ?: Custom(value) - - {{#allowableValues}}{{#enumVars}} - inline val {{#lambda.unquote}}{{&value}}{{/lambda.unquote}}: {{classname}} - get() = Supported.{{#lambda.unquote}}{{&value}}{{/lambda.unquote}} - {{/enumVars}}{{/allowableValues}} - - @JvmStatic fun values(): List<{{classname}}> = Supported.entries - } -} - -object {{classname}}Serializer : KSerializer<{{classname}}> { - private val valueSerializer = {{{dataType}}}.serializer() - override val descriptor = valueSerializer.descriptor - - override fun deserialize(decoder: Decoder): {{classname}} { - val value = decoder.decodeSerializableValue(valueSerializer) - return {{classname}}.valueOf(value) - } - - override fun serialize(encoder: Encoder, value: {{classname}}) { - encoder.encodeSerializableValue(valueSerializer, value.value) - } -} diff --git a/openai-client/generator/config/model.mustache b/openai-client/generator/config/model.mustache deleted file mode 100644 index bf6667742..000000000 --- a/openai-client/generator/config/model.mustache +++ /dev/null @@ -1,17 +0,0 @@ -{{>licenseInfo}} -package {{modelPackage}} - -{{#imports}}import {{import}} -{{/imports}} -{{#models}} -{{#model}} -{{! We execute this block only for the _first_ element: if oneOf is not empty, execute the template }} -{{#oneOf}}{{#-first}}{{>model_oneof}}{{/-first}}{{/oneOf}}{{^oneOf}} -{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}} -{{#isAlias}}typealias {{classname}} = {{{dataType}}}{{/isAlias}}{{^isAlias}} -{{>data_class}} -{{/isAlias}} -{{/isEnum}} -{{/oneOf}} -{{/model}} -{{/models}} \ No newline at end of file diff --git a/openai-client/generator/config/model_oneof.mustache b/openai-client/generator/config/model_oneof.mustache deleted file mode 100644 index 132176c5d..000000000 --- a/openai-client/generator/config/model_oneof.mustache +++ /dev/null @@ -1,41 +0,0 @@ -import com.xebia.functional.openai.attemptDeserialize -import kotlin.jvm.JvmInline -import kotlinx.serialization.* -import kotlinx.serialization.builtins.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.* - -@Serializable(with = {{classname}}Serializer::class) -sealed interface {{classname}} { - - {{#oneOf}} - @JvmInline - @Serializable - value class Case{{#lambda.oneOfName}}{{{.}}}{{/lambda.oneOfName}}(val value: {{{.}}}): {{classname}} - {{/oneOf}} - -} - -object {{classname}}Serializer : KSerializer<{{classname}}> { - @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) - override val descriptor: SerialDescriptor = buildSerialDescriptor("{{classname}}", PolymorphicKind.SEALED) { {{#oneOf}} - element("{{#lambda.oneOfName}}{{-index}}{{/lambda.oneOfName}}", {{#lambda.serializer}}{{{.}}}{{/lambda.serializer}}.descriptor){{/oneOf}} - } - - override fun deserialize(decoder: Decoder): {{classname}} { - val jsonSerde = Json { ignoreUnknownKeys = true } - val json = decoder.decodeSerializableValue(JsonElement.serializer()) - return attemptDeserialize(json, {{#oneOf}} - Pair({{classname}}.Case{{#lambda.oneOfName}}{{{.}}}{{/lambda.oneOfName}}::class) { - {{classname}}.Case{{#lambda.oneOfName}}{{{.}}}{{/lambda.oneOfName}}(jsonSerde.decodeFromJsonElement({{#lambda.serializer}}{{{.}}}{{/lambda.serializer}}, json)) - }{{#-last}}){{/-last}}{{^-last}},{{/-last}}{{/oneOf}} - } - - override fun serialize(encoder: Encoder, value: {{classname}}) = - when(value) { {{#oneOf}} - is {{classname}}.Case{{#lambda.oneOfName}}{{{.}}}{{/lambda.oneOfName}} -> - encoder.encodeSerializableValue({{#lambda.serializer}}{{{.}}}{{/lambda.serializer}}, value.value) - {{/oneOf}} - } -} diff --git a/openai-client/generator/config/openai-api-commit b/openai-client/generator/config/openai-api-commit deleted file mode 100644 index 94b46cf74..000000000 --- a/openai-client/generator/config/openai-api-commit +++ /dev/null @@ -1 +0,0 @@ -df5699f5c48fe455d9d036198e77fdb637edee29 diff --git a/openai-client/generator/config/openai.mustache b/openai-client/generator/config/openai.mustache deleted file mode 100644 index f9cc7a5fc..000000000 --- a/openai-client/generator/config/openai.mustache +++ /dev/null @@ -1,26 +0,0 @@ -{{#apiInfo}} -{{>licenseInfo}} -package {{apiPackage}} - -import com.xebia.functional.openai.Config -import io.ktor.client.HttpClient -{{#imports}}import {{import}} -{{/imports}} -{{#apis}}import com.xebia.functional.openai.generated.api.{{classFilename}} -{{/apis}} - -interface OpenAI : AutoCloseable { -{{#apis}} - val {{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}}: {{classFilename}} -{{/apis}} -} - -fun OpenAI(client: HttpClient, config: Config): OpenAI = object : OpenAI, AutoCloseable { -{{#apis}} - override val {{#lambda.camelcase}}{{classFilename}}{{/lambda.camelcase}} = {{classFilename}}(client, config) -{{/apis}} - - override fun close() = client.close() -} - -{{/apiInfo}} \ No newline at end of file diff --git a/openai-client/generator/gradle.properties b/openai-client/generator/gradle.properties deleted file mode 100644 index 1eda1733c..000000000 --- a/openai-client/generator/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -org.gradle.jvmargs="-Dlog.level=debug --global-property debugModels" \ No newline at end of file diff --git a/openai-client/generator/src/main/java/ai/xef/openai/generator/DownloadOpenAIAPI.java b/openai-client/generator/src/main/java/ai/xef/openai/generator/DownloadOpenAIAPI.java deleted file mode 100644 index e0570e9fc..000000000 --- a/openai-client/generator/src/main/java/ai/xef/openai/generator/DownloadOpenAIAPI.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.xef.openai.generator; - -import java.io.*; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; - -public class DownloadOpenAIAPI { - public static void main(String[] args) { - try { - String commit = readCommit(); - downloadAPI(commit); - - // list streaming methods - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static String readCommit() throws IOException { - StringBuilder resultStringBuilder = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new FileReader("config/openai-api-commit"))) { - String line; - while ((line = br.readLine()) != null) { - resultStringBuilder.append(line).append("\n"); - } - } - return resultStringBuilder.toString().trim(); - } - - private static void downloadAPI(String commit) throws IOException { - URL url = new URL("https://raw.githubusercontent.com/openai/openai-openapi/%s/openapi.yaml".formatted(commit)); - ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); - try (FileOutputStream fileOutputStream = new FileOutputStream("config/openai-api.yaml")) { - FileChannel fileChannel = fileOutputStream.getChannel(); - fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); - } - } -} diff --git a/openai-client/generator/src/main/java/ai/xef/openai/generator/KMMGeneratorConfig.java b/openai-client/generator/src/main/java/ai/xef/openai/generator/KMMGeneratorConfig.java deleted file mode 100644 index 2017ec89c..000000000 --- a/openai-client/generator/src/main/java/ai/xef/openai/generator/KMMGeneratorConfig.java +++ /dev/null @@ -1,308 +0,0 @@ -package ai.xef.openai.generator; - -import com.google.common.collect.ImmutableMap; -import com.samskivert.mustache.Mustache; -import com.samskivert.mustache.Template; -import org.apache.commons.lang3.NotImplementedException; -import org.apache.commons.lang3.tuple.Pair; -import org.openapitools.codegen.*; -import org.openapitools.codegen.languages.KotlinClientCodegen; -import org.openapitools.codegen.model.ModelMap; -import org.openapitools.codegen.model.ModelsMap; -import org.openapitools.codegen.model.OperationsMap; - -import java.io.IOException; -import java.io.Writer; -import java.util.*; - -import static java.util.Map.entry; - -@SuppressWarnings("unused") -public class KMMGeneratorConfig extends KotlinClientCodegen { - - - private final Map> nonRequiredFields = new LinkedHashMap<>(); - - @Override - public String getIgnoreFilePathOverride() { - return ".openapi-generator-ignore"; - } - - public KMMGeneratorConfig() { - super(); - - // Generate in src/commonMain/kotlin, not /src/main/kotlin - additionalProperties.put("sourceFolder", "src/commonMain/kotlin"); - additionalProperties.put("generateModelTests", false); - additionalProperties.put("generateApiTests", false); - additionalProperties.put("generateInfrastructure", false); - setModelPackage("com.xebia.functional.openai.generated.model"); - setApiPackage("com.xebia.functional.openai.generated.api"); - additionalProperties.put("apiSuffix", ""); - additionalProperties.put("modelSuffix", ""); - - // Configure OpenAI `object` to be mapped to `JsonObject` - typeMapping.put("object", "JsonObject"); - importMapping.put("JsonObject", "kotlinx.serialization.json.JsonObject"); - - typeMapping.put("java.net.URI", "kotlin.String"); - - typeMapping.put("java.math.BigDecimal", "kotlin.Double"); - importMapping.put("BigDecimal", "kotlin.Double"); - - typeMapping.put("java.io.File", "UploadFile"); - importMapping.put("java.io.File", "com.xebia.functional.openai.UploadFile"); - - // Maps `Map` to `JsonObject` - schemaMapping.put("FunctionParameters", "kotlinx.serialization.json.JsonObject"); - - // Configure the template directory - templateDir = "config"; - supportingFiles.add(new SupportingFile("openai.mustache", "src/commonMain/kotlin/" + apiPackage.replace(".", "/"), "OpenAI.kt")); - - omitGradleWrapper = true; - serializationLibrary = SERIALIZATION_LIBRARY_TYPE.kotlinx_serialization; - - defaultIncludes.remove("io.ktor.client.request.forms.InputProvider"); - defaultIncludes.add("com.xebia.functional.openai.apis.UploadFile"); - - importMapping.remove("InputProvider"); - importMapping.put("UploadFile", "com.xebia.functional.openai.apis.UploadFile"); - - // Fixes for DateTime - dateLibrary = DateLibrary.KOTLINX_DATETIME.value; - typeMapping.put("date", "kotlinx.datetime.LocalDate"); - typeMapping.put("date-time", "kotlinx.datetime.Instant"); - typeMapping.put("DateTime", "Instant"); - importMapping.put("Instant", "kotlinx.datetime.Instant"); - - specialCharReplacements.put("-", "_"); - specialCharReplacements.put(".", "_"); - // TODO PascalCase!?! - enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.snake_case; - nonRequiredFields.putAll( - Map.ofEntries( - entry("ListAssistantFilesResponse", List.of("firstId", "lastId")), - entry("ListAssistantsResponse", List.of("firstId", "lastId")), - entry("ListMessageFilesResponse", List.of("firstId", "lastId")), - entry("ListMessagesResponse", List.of("firstId", "lastId")), - entry("ListRunsResponse", List.of("firstId", "lastId")), - entry("ListRunStepsResponse", List.of("firstId", "lastId")), - entry("ListThreadsResponse", List.of("firstId", "lastId")), - entry("MessageObject", List.of("status", "metadata")), - entry("MessageObjectContentInner", List.of("imageFile", "text")), - entry("RunObject", List.of("expiresAt", "requiredAction")), - entry("RunStepDetailsToolCallsCodeObjectCodeInterpreterOutputsInner", List.of("logs", "image")), - entry("RunStepDetailsToolCallsFunctionObjectFunction", List.of("name", "arguments", "output")), - entry("RunStepDetailsToolCallsFunctionObject", List.of("id")), - entry("RunStepDetailsToolCallsObjectToolCallsInner", List.of("codeInterpreter", "retrieval", "function")), - entry("RunStepDetailsToolCallsRetrievalObject", List.of("retrieval")), - entry("RunStepObject", List.of("expiredAt", "metadata")), - entry("MessageContentTextObjectTextAnnotationsInner", List.of("filePath", "fileCitation")) - ) - ); - } - - private Optional readEnumModel(List all) { - if (all.size() == 2) { - CodegenProperty first = all.get(0); - CodegenProperty second = all.get(1); - if (first.isString && second.isEnum) { - return Optional.of(second); - } - } - return Optional.empty(); - } - - /** - * Map - * Used to generate additional code for operations that support streaming. - *

- * Extra streaming operation will be generated for OperationId, and the return type will be Flow. - */ - private final static Map> streamingOps = Map.of( - "createThreadAndRun", Pair.of("com.xebia.functional.openai", "ServerSentEvent"), - "createRun", Pair.of("com.xebia.functional.openai", "ServerSentEvent"), - "submitToolOuputsToRun", Pair.of("com.xebia.functional.openai", "ServerSentEvent"), - "createChatCompletion", Pair.of("com.xebia.functional.openai.generated.model", "CreateChatCompletionStreamResponse") - ); - - /** - * Add the `x-streaming` vendor extension to the operations that are streaming, - * and add `x-streaming-return` of the return type of the operation. - *

- * This is used in the mustache template to generate additional code for operations that support streaming. - */ - @Override - public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { - objs.getOperations() - .getOperation() - .forEach((op) -> { - if (streamingOps.containsKey(op.operationId)) { - op.vendorExtensions.put("x-streaming", true); - Pair returnType = streamingOps.get(op.operationId); - Map imports = Map.of( - "import", returnType.getKey() + "." + returnType.getValue(), - "classname", returnType.getValue() - ); - if(!objs.getImports().contains(imports)) { - objs.getImports().add(imports); - } - op.vendorExtensions.put("x-streaming-return", returnType.getValue()); - } - } - ); - return super.postProcessOperationsWithModels(objs, allModels); - } - - @Override - public ModelsMap postProcessModels(ModelsMap objs) { - for (ModelMap mo : objs.getModels()) { - CodegenModel cm = mo.getModel(); - if (cm.anyOf != null && !cm.anyOf.isEmpty()) { - Optional codegenProperty = readEnumModel(cm.getComposedSchemas().getAnyOf()); - codegenProperty.ifPresent((enumProp) -> { - cm.modelJson = enumProp.jsonSchema; - cm.interfaces = null; - cm.anyOf = new HashSet<>(); - cm.dataType = enumProp.dataType; - cm.isString = enumProp.isString; - cm.allowableValues = enumProp.allowableValues; - cm.isEnum = enumProp.isEnum; - cm.setIsAnyType(enumProp.isAnyType); - cm.setComposedSchemas(null); - }); - } else if (nonRequiredFields.containsKey(cm.classname)) { - List fields = nonRequiredFields.getOrDefault(cm.classname, Collections.emptyList()); - cm - .allVars - .stream() - .filter(p -> fields.contains(p.name)) - .forEach(p -> p.setRequired(false)); - } - } - return super.postProcessModels(objs); - } - - @Override - protected ImmutableMap.Builder addMustacheLambdas() { - return super.addMustacheLambdas() - .put("oneOfName", new OneOfName()) - .put("capitalised", new Capitalised()) - .put("unquote", new Unquote()) - .put("jsname", new JsName()) - .put("serializer", new Serializer()) - .put("dropslash", new DropSlash()); - } - - /* Mechanism to do array access in mustache... - * We need to generate names for the cases of `oneOf`, - * where we generate a `sealed interface` with the Schema name, - * and a `data class CaseInnerType(val value: InnerType)` for each of the `oneOf` cases. */ - public static class OneOfName implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - serializer(writer, fragment.execute().trim(), 0); - } - - private void serializer( - Writer buffer, - String text, - int depth - ) throws IOException { - if (text.startsWith("kotlin.collections.List<")) { - String inner = text.substring(24, text.length() - 1); - serializer(buffer, inner, depth + 1); - } else if (text.startsWith("kotlin.collections.")) { - throw new NotImplementedException(text + " collection serialization not supported **yet**"); - } else { - if (text.startsWith("kotlin.")) { - text = text.substring(7); - } - buffer.write(text); - if (depth > 0) { - buffer.write("s"); - depth--; - } - while (depth-- > 0) { - buffer.write("List"); - } - } - } - } - - /* Lambda to generate the `@JsName` annotation for the `length` property, - * can be generalised to other properties/names if needed */ - public static class JsName implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - String text = fragment.execute(); - if (text.equals("length")) { - writer.write("@JsName(\"length_type\") length"); - } else { - writer.write(text); - } - } - } - - /* Lambda to capitalise the first letter of a string, and lowercase the rest */ - public static class Capitalised implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - String text = fragment.execute(); - writer.write(text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase(Locale.ROOT)); - } - } - - /* Drop first `/` from a path */ - public static class DropSlash implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - String text = fragment.execute(); - if (text.startsWith("/")) { - writer.write(text.substring(1)); - } else { - writer.write(text); - } - } - } - - /* enum with `gpt-` are trimmed by `gpt_`, the raw value needs to trim the surrounding `"` */ - public static class Unquote implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - String text = snakeCase(fragment.execute()); - if (text.startsWith("\"") && text.endsWith("\"")) { - writer.write(text.substring(1, text.length() - 1)); - } else { - writer.write(text); - } - } - - // Poor man lower snakeCase - private String snakeCase(String text) { - return text.replace("-", "_").replace(".", "_"); - } - } - - /* Most advanced lambda, to generate the `serializer()` call for the `kotlinx.serialization` library. - * This works for ListSerializer, but can work for much more!! */ - public static class Serializer implements Mustache.Lambda { - public void execute(Template.Fragment fragment, Writer writer) throws IOException { - serializer(writer, fragment.execute().trim(), 0); - } - - private void serializer( - Writer buffer, - String text, - int depth - ) throws IOException { - if (text.startsWith("kotlin.collections.List<")) { - String inner = text.substring(24, text.length() - 1); - buffer.write("ListSerializer("); - serializer(buffer, inner, depth + 1); - } else if (text.startsWith("Map<")) { - throw new NotImplementedException("Map serialization not supported **yet**"); - } else { - buffer.write(text); - buffer.write(".serializer()"); - buffer.write(")".repeat(Math.max(0, depth))); - } - } - } -} diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt index 6f764ea4e..6b8f74d81 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt @@ -1,8 +1,8 @@ package com.xebia.functional.xef.server.services -import com.xebia.functional.openai.generated.model.CreateEmbeddingRequestModel import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI +import com.xebia.functional.xef.openapi.CreateEmbeddingRequest import com.xebia.functional.xef.store.PGVectorStore import com.xebia.functional.xef.store.VectorStore import com.xebia.functional.xef.store.postgresql.PGDistanceStrategy @@ -21,8 +21,8 @@ class PostgresVectorStoreService( private val preDeleteCollection: Boolean = false, private val chunkSize: Int = 400, private val distanceStrategy: PGDistanceStrategy = PGDistanceStrategy.Euclidean, - private val embeddingRequestModel: CreateEmbeddingRequestModel = - CreateEmbeddingRequestModel.text_embedding_3_small + private val embeddingRequestModel: CreateEmbeddingRequest.Model = + CreateEmbeddingRequest.Model.TextEmbedding3Small ) : VectorStoreService() { fun addCollection() { diff --git a/settings.gradle.kts b/settings.gradle.kts index dbb1da79e..ebf2bdfed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,13 +26,6 @@ project(":xef-filesystem").projectDir = file("filesystem") include("xef-tokenizer") project(":xef-tokenizer").projectDir = file("tokenizer") - -include("xef-openai-client") -project(":xef-openai-client").projectDir = file("openai-client/client") - -include("xef-openai-client-generator") -project(":xef-openai-client-generator").projectDir = file("openai-client/generator") - // //