🌐 English | 中文
The name "Koaks" is homophonic with "coax".
🧩 Connect your tools, compose your logic, rule your agents.
- Kotlin 2.x / JDK 21 or higher
- Maven or Gradle build tool
- An available LLM API Key (e.g., OpenAI, DeepSeek)
- 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>Warning: The current project is in a rapid iteration phase, and the API may change at any time.
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)
}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)
}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()
}// 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)
}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()
}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
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. |
