Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/create-version-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/openai-api-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-development-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Expand Down Expand Up @@ -65,3 +63,7 @@ fun getGradleCommand(platform: String): String {
configureBuildAndTestTask("buildAndTestMultip", ModulePlatformType.MULTI)
configureBuildAndTestTask("buildAndTestSinglep", ModulePlatformType.SINGLE)

tasks.register<DownloadOpenAI>("downloadOpenAIAPI") {
input.set(file("core/openai-api-commit"))
output.set(file("core/openai-api.yaml"))
}
45 changes: 45 additions & 0 deletions buildSrc/src/main/kotlin/DownloadOpenAI.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
150 changes: 78 additions & 72 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions core/openai-api-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
df5699f5c48fe455d9d036198e77fdb637edee29
File renamed without changes.
24 changes: 14 additions & 10 deletions core/src/commonMain/kotlin/com/xebia/functional/xef/AI.kt
Original file line number Diff line number Diff line change
@@ -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<out A>(private val config: AIConfig, val serializer: Tool<A>) {

Expand Down Expand Up @@ -59,17 +61,19 @@ class AI<out A>(private val config: AIConfig, val serializer: Tool<A>) {
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Tool<*>> = 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,
Expand Down
15 changes: 2 additions & 13 deletions core/src/commonMain/kotlin/com/xebia/functional/xef/Config.kt
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading