Skip to content

koaks-ai/koaks

Repository files navigation

koaks

🌐   English | 中文

The name "Koaks" is homophonic with "coax".

koaks-all

koaks

🧩 Connect your tools, compose your logic, rule your agents.

🚀 Quick Start

1. Preparation

  • Kotlin 2.x / JDK 21 or higher
  • Maven or Gradle build tool
  • An available LLM API Key (e.g., OpenAI, DeepSeek)

2. Add Dependencies

  • Gradle (Kotlin DSL)
// For Gradle projects, whether it's a JVM project or a Kotlin Multiplatform project, 
// you only need to add the following. Gradle will automatically handle platform adaptation.
implementation("io.github.mynna404:koaks-core:0.0.1-preview6")
implementation("io.github.mynna404:koaks-qwen:0.0.1-preview6")
  • Maven
<!-- For Maven projects, you need to distinguish between different platforms yourself. 
     Of course, if you’re not sure what that means, you can simply add the following to your pom.xml. -->
<dependency>
  <groupId>io.github.mynna404</groupId>
  <artifactId>koaks-core-jvm</artifactId>
  <version>0.0.1-preview6</version>
</dependency>

<dependency>
  <groupId>org.koaks.framework</groupId>
  <artifactId>koaks-qwen-jvm</artifactId>
  <version>0.0.1-preview6</version>
</dependency>

3. Simple

Warning: The current project is in a rapid iteration phase, and the API may change at any time.

Connect to ChatGPT and Send a Message

suspend fun main() {
    val client = createChatClient {
        model {
            qwen(
                baseUrl = "base-url",
                apiKey = "api-key",
                modelName = "qwen3-235b-a22b-instruct-2507",
            )
        }
    }

    val result = client.generate("What's the meaning of life?")
    println(result)
}

Chat with Memory

suspend fun main() {
    val client = createChatClient {
        model {
            qwen(
                baseUrl = "base-url",
                apiKey = "api-key",
                modelName = "qwen3-235b-a22b-instruct-2507",
            )
        }
        memory {
            // manually manage message history
            // none()
            default()
        }
    }

    val result = client.chatWithMemory("What's the meaning of life?", "1001")
    println(resp0.value().choices?.getOrNull(0)?.message?.content)
}

Streaming Response

suspend fun main() {
    val client = createChatClient {
        model {
            qwen(
                baseUrl = "base-url",
                apiKey = "api-key",
                modelName = "qwen3-235b-a22b-instruct-2507",
            ) {
                params {
                    stream = true
                }
            }
        }
    }

    val chatRequest = ChatRequest(
        message = Message.userText("What's the meaning of life?")
    )

    val result = client.chat(chatRequest)

    result.stream().map { data ->
        print(data.choices?.get(0)?.delta?.content)
    }.collect()

}

Tool Call (JVM and all platform)

// only JVM
class WeatherToolsForJvm {

    @Tool(
        params = [
            Param(param = "city", description = "city name, like Shanghai", required = true),
            Param(param = "date", description = "date, like 2025-08-17", required = true)
        ],
        group = "weather",
        description = "Get the weather for a specific city today."
    )
    fun getWeather(city: String, date: String): String {
        return "For $city on $date, the weather is cloudy with a high wind warning."
    }

    @Tool(
        group = "location",
        description = "Get the city where the user is located"
    )
    fun getCity(): String {
        return "Shanghai"
    }

}


fun main() {
    Koaks.init(arrayOf("your package"))
    runBlocking {
        val client = createChatClient {
            model {
                qwen(
                    baseUrl = "base-url",
                    apiKey = "api-key",
                    modelName = "qwen3-235b-a22b-instruct-2507",
                ) {
                    params {
                        stream = true
                        parallelToolCalls = true
                    }
                }
            }
            memory {
                default()
            }
            tools {
                groups("weather", "location")
            }
        }

        val chatRequest = ChatRequest(
            message = Message.userText("What's the weather like?")
        )

        val result = client.chat(chatRequest)

        println(result.value.choices?.getOrNull(0)?.message?.content)
    }

}
// for all platform

// use Tool interface
class WeatherImplTools : Tool<WeatherInput> {

    override val name: String = "getWeather"
    override val description: String = "get the weather for a specific city today."
    override val group: String = "weather"
    override val serializer: KSerializer<WeatherInput> = WeatherInput.serializer()
    override val returnDirectly: Boolean = false

    override suspend fun execute(input: WeatherInput): String {
        return "For ${input.city} on ${input.date}, the weather is cloudy with a high wind warning."
    }

}

@Serializable
class WeatherInput(
    @Description("city name, like Shanghai")
    val city: String,
    @Description("date, like 2025-08-17")
    val date: String
)

class UserImplTools() : Tool<NoneInput> {

    override val name: String = "userLocation"
    override val description: String = "get the city where the user is located"
    override val group: String = "location"
    override val serializer: KSerializer<NoneInput> = NoneInput.serializer()
    override val returnDirectly: Boolean = false

    override suspend fun execute(input: NoneInput): String {
        return "Shanghai"
    }

}

// ---------------------------------------------
// use dsl
val weatherTool = createTool<WeatherInput>(
    name = "getWeather",
    description = "get the weather for a specific city today.",
    group = "weather"
) { input ->
    "The weather in ${input.city} at ${input.date} is windy."
}

val locationTool = createTool<NoneInput>(
    name = "userLocation",
    description = "get the city where the user is located",
    group = "location"
) { _ ->
    "Shanghai"
}

fun main() = runBlocking {
    val client = createChatClient {
        model {
            qwen(
                baseUrl = EnvTools.loadValue("BASE_URL"),
                apiKey = EnvTools.loadValue("API_KEY"),
                modelName = "qwen3-235b-a22b-instruct-2507",
            ){
                params {
                    parallelToolCalls = true
                }
            }
        }
        tools {
            addTools(
                UserImplTools(),
                WeatherImplTools()
            )
            // dsl
//            addTools(
//                weatherTool, locationTool
//            )
            groups("weather", "location")
        }
    }

    val chatRequest = ChatRequest(
        message = Message.userText("What's the 'shanghai'、'beijing'、'xi an'、'tai an' weather like?")
    )

    val result = client.chat(chatRequest)

    println(result.value().choices?.getOrNull(0)?.message?.content)
}

Use Multimodal Model

val multimodalClient = createChatClient {
    model {
        qwen(
            baseUrl = EnvTools.loadValue("BASE_URL"),
            apiKey = EnvTools.loadValue("API_KEY"),
            modelName = "qwen-omni-turbo-2025-03-26",
        ) {
            params {
                stream = true
                modalities = listOf(ModalitiesType.TEXT, ModalitiesType.AUDIO)
                streamOptions = StreamOptions(true)
            }
        }
    }
    memory {
        default()
    }
}

@Test
fun testImageInput() = runBlocking {
    val resp0 = multimodalClient.chat(
        ChatRequest(
            message = Message.multimodal(
                Message.userImageUrl("https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"),
                Message.userText("What is in the picture?")
            )
        )
    )

    resp0.stream().onEach {
        print(it.choices?.get(0)?.delta?.content)
    }.collect()
}

@Test
fun testAudioInput() = runBlocking {
    val resp0 = multimodalClient.chatWithMemory(
        ChatRequest(
            message = Message.multimodal(
                Message.userAudio(
                    "https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3",
                    "mp3"
                ),
                Message.userText("What is in the audio?")
            )
        ), "testAudioInput"
    )

    println("===== first =====")
    resp0.stream().onEach {
        print(it.choices?.get(0)?.delta?.content)
    }.collect()

    val resp1 = multimodalClient.chatWithMemory(
        ChatRequest(
            message = Message.userText("introduce the company")
        ), "testAudioInput"
    )

    println("===== second =====")
    resp1.stream().onEach {
        print(it.choices?.get(0)?.delta?.content)
    }.collect()

}

@Test
fun testVideoFrameInput() = runBlocking {
    val resp0 = multimodalClient.chat(
        ChatRequest(
            message = Message.multimodal(
                Message.userVideoFrame(
                    "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/xzsgiz/football1.jpg",
                    "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/tdescd/football2.jpg",
                    "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/zefdja/football3.jpg",
                    "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241108/aedbqh/football4.jpg",
                ),
                Message.userText("What is in the video?")
            )
        )
    )

    resp0.stream().onEach {
        print(it.choices?.get(0)?.delta?.content)
    }.collect()
}

@Test
fun testVideoUrlInput() = runBlocking {
    val resp0 = multimodalClient.chat(
        ChatRequest(
            message = Message.multimodal(
                Message.userVideoUrl(
                    "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241115/cqqkru/1.mp4"
                ),
                Message.userText("What is in the video?")
            )
        )
    )

    resp0.stream().onEach {
        print(it.choices?.get(0)?.delta?.content)
    }.collect()
}

🤝 Contributing Guide

Thank you for your interest in contributing! You are welcome to contribute code, improve documentation, or submit issues.

1.	Fork the repository
2.	Create a new branch (git checkout -b feature-xxx)
3.	Commit your changes (git commit -m 'Add new feature')
4.	Push your branch (git push origin feature-xxx)
5.	Create a Pull Request

💖 Acknowledgements

This project makes use of, but is not limited to, the following open-source projects:

Project Description
Kotlin The Kotlin Programming Language.
kotlin-logging Lightweight multiplatform logging framework for Kotlin. A convenient and performant logging facade.