Skip to content
Merged
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
3 changes: 2 additions & 1 deletion android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ dependencies {
implementation(libs.koog.agents)
implementation(libs.leap.sdk)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.koog.edge)
// implementation(libs.koog.edge)
implementation(project(":koog-edge"))
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ package io.github.lemcoder.koogedge.agents.calculator
import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.agent.config.AIAgentConfig
import ai.koog.agents.core.agent.functionalStrategy
import ai.koog.agents.core.dsl.extension.asAssistantMessage
import ai.koog.agents.core.dsl.extension.executeTool
import ai.koog.agents.core.dsl.extension.requestLLM
import ai.koog.agents.core.environment.result
import ai.koog.agents.core.tools.ToolRegistry
import ai.koog.prompt.dsl.prompt
import ai.koog.prompt.executor.llms.SingleLLMPromptExecutor
import ai.koog.prompt.message.Message
import android.util.Log
import io.github.lemcoder.koog.edge.cactus.CactusLLMParams
import io.github.lemcoder.koog.edge.cactus.CactusModels
import io.github.lemcoder.koog.edge.cactus.getCactusLLMClient
import io.github.lemcoder.koog.edge.leap.LeapModels
import io.github.lemcoder.koog.edge.leap.getLeapLLMClient
import io.github.lemcoder.koogedge.App
import io.github.lemcoder.koogedge.agents.common.AgentProvider
Expand Down Expand Up @@ -78,12 +75,12 @@ internal class CalculatorAgentProvider : AgentProvider {
prompt("test", params = CactusLLMParams(maxTokens = 512)) {
system(calculatorSystemPrompt)
},
model = CactusModels.Chat.Qwen3_0_6B,
model = LeapModels.Chat.LFM2_1_2B_Instruct,
maxAgentIterations = 10,
)

return AIAgent(
promptExecutor = cactusExecutor,
promptExecutor = leapExecutor,
strategy = strategy,
agentConfig = agentConfig,
toolRegistry = toolRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package io.github.lemcoder.koogedge.agents.chat
import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.agent.config.AIAgentConfig
import ai.koog.agents.core.agent.functionalStrategy
import ai.koog.agents.core.dsl.extension.requestLLM
import ai.koog.prompt.dsl.prompt
import ai.koog.prompt.executor.llms.SingleLLMPromptExecutor
import io.github.lemcoder.koog.edge.cactus.CactusLLMParams
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package io.github.lemcoder.koogedge.agents.common

const val modelsPath = "/tmp/models"
import io.github.lemcoder.koogedge.App

val modelsPath: String = App.context.filesDir.absolutePath
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@ package io.github.lemcoder.koogedge.agents.weather
import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.agent.config.AIAgentConfig
import ai.koog.agents.core.agent.functionalStrategy
import ai.koog.agents.core.dsl.extension.asAssistantMessage
import ai.koog.agents.core.dsl.extension.executeTool
import ai.koog.agents.core.dsl.extension.requestLLM
import ai.koog.agents.core.dsl.extension.sendToolResult
import ai.koog.agents.core.tools.ToolRegistry
import ai.koog.prompt.dsl.prompt
import ai.koog.prompt.executor.llms.SingleLLMPromptExecutor
import ai.koog.prompt.message.Message
import io.github.lemcoder.koog.edge.cactus.CactusModels
import io.github.lemcoder.koog.edge.cactus.getCactusLLMClient
import io.github.lemcoder.koog.edge.leap.LeapModels
import io.github.lemcoder.koog.edge.leap.getLeapLLMClient
import io.github.lemcoder.koogedge.App
import io.github.lemcoder.koogedge.agents.common.AgentProvider
Expand Down Expand Up @@ -63,13 +59,13 @@ internal class WeatherAgentProvider : AgentProvider {
.trimIndent()
)
},
model = CactusModels.Chat.Qwen3_0_6B,
model = LeapModels.Chat.LFM2_1_2B_Instruct,
maxAgentIterations = 50,
)

// Return the agent
return AIAgent(
promptExecutor = cactusExecutor,
promptExecutor = leapExecutor,
strategy = strategy,
agentConfig = agentConfig,
toolRegistry = toolRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@ package io.github.lemcoder.koogedge.agents.weather

import ai.koog.agents.core.tools.Tool
import ai.koog.agents.core.tools.annotations.LLMDescription
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.offsetAt
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/** Tools for the weather agent */
object WeatherTools {
private val openMeteoClient = OpenMeteoClient()

private val UTC_ZONE = TimeZone.UTC
private val UTC_ZONE: ZoneId = ZoneOffset.UTC

/** Granularity options for weather forecasts */
@Serializable
Expand Down Expand Up @@ -54,25 +51,18 @@ object WeatherTools {
override suspend fun execute(args: Args): Result {
val zoneId =
try {
TimeZone.of(args.timezone)
ZoneId.of(args.timezone)
} catch (_: Exception) {
UTC_ZONE
}

val now = Clock.System.now()
val localDateTime = now.toLocalDateTime(zoneId)
val offset = zoneId.offsetAt(now)

val time = localDateTime.time
val timeStr =
"${time.hour.toString().padStart(2, '0')}:${
time.minute.toString().padStart(2, '0')
}:${time.second.toString().padStart(2, '0')}"
val now = ZonedDateTime.now(zoneId)
val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss")

return Result(
datetime = "${localDateTime.date}T$timeStr$offset",
date = localDateTime.date.toString(),
time = timeStr,
datetime = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
date = now.toLocalDate().toString(),
time = now.toLocalTime().format(timeFormatter),
timezone = zoneId.id,
)
}
Expand Down Expand Up @@ -112,25 +102,21 @@ object WeatherTools {
LocalDate.parse(args.date)
} catch (_: Exception) {
// Use current date if parsing fails
Clock.System.now().toLocalDateTime(UTC_ZONE).date
LocalDate.now(UTC_ZONE)
}
} else {
Clock.System.now().toLocalDateTime(UTC_ZONE).date
LocalDate.now(UTC_ZONE)
}

// Convert to LocalDateTime to handle hours and minutes
val baseDateTime =
LocalDateTime(baseDate.year, baseDate.month, baseDate.dayOfMonth, 0, 0)
val baseInstant = baseDateTime.toInstant(UTC_ZONE)

val period =
DateTimePeriod(days = args.days, hours = args.hours, minutes = args.minutes)

val newInstant = baseInstant.plus(period, UTC_ZONE)
val resultDate = newInstant.toLocalDateTime(UTC_ZONE).date.toString()
val baseDateTime = baseDate.atStartOfDay(UTC_ZONE)
val resultDateTime =
baseDateTime
.plusDays(args.days.toLong())
.plusHours(args.hours.toLong())
.plusMinutes(args.minutes.toLong())

return Result(
date = resultDate,
date = resultDateTime.toLocalDate().toString(),
originalDate = args.date,
daysAdded = args.days,
hoursAdded = args.hours,
Expand Down Expand Up @@ -167,7 +153,7 @@ object WeatherTools {
)

override suspend fun execute(args: Args): Result {
val date = Clock.System.now().toString()
val date = Instant.now().toString()
// Search for the location
val locations = openMeteoClient.searchLocation(args.location)
if (locations.isEmpty()) {
Expand Down Expand Up @@ -205,8 +191,7 @@ object WeatherTools {
private fun formatDailyForecast(forecast: WeatherForecast, date: String): String {
val daily = forecast.daily ?: return "No daily forecast data available"

val startDate =
date.ifBlank { Clock.System.now().toLocalDateTime(UTC_ZONE).date.toString() }
val startDate = date.ifBlank { LocalDate.now(UTC_ZONE).toString() }

val startIndex = daily.time.indexOfFirst { it >= startDate }.coerceAtLeast(0)

Expand Down
16 changes: 8 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[versions]
compileSdk = "36"
koogEdge = "0.0.2"
koogEdge = "0.0.3"
minSdk = "31"

agp = "9.0.0"
kotlin = "2.3.0"
coreKtx = "1.17.0"
agp = "9.1.0"
kotlin = "2.3.20"
coreKtx = "1.18.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.12.2"
composeBom = "2026.01.00"
activityCompose = "1.13.0"
composeBom = "2026.03.01"

koog = "0.6.0"
leap = "0.9.2" # TODO Update to latest version and migrate
koog = "0.7.3"
leap = "0.9.7" # TODO Update to latest version and migrate
cactus = "1.3.0-beta"

kotlinx-serialization = "1.10.0"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Oct 13 23:26:58 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion koog-edge/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = "io.github.lemcoder"
version = "0.0.3"
version = "0.0.4"

kotlin {
jvmToolchain(17)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import io.github.lemcoder.koog.edge.provider.LocalLLMProvider
* isDownloaded=false, quantization=8)
*/
sealed interface CactusModels : LocalModel {
data object Chat : LocalModel {
data object Chat : CactusModels {
val FunctionGemma_270M =
LLModel(
provider = LocalLLMProvider,
Expand Down Expand Up @@ -140,6 +140,26 @@ sealed interface CactusModels : LocalModel {
capabilities = listOf(LLMCapability.Tools, LLMCapability.Completion),
contextLength = 16_384,
)
private var customModels: List<LLModel> = emptyList()
override val models: List<LLModel>
get() =
listOf(
FunctionGemma_270M,
LFM2_1_2B_Tool,
Qwen3_0_6B,
Qwen3_1_7B,
LFM2_1_2B,
LFM2_1_2B_RAG,
LFM2_350M,
LFM2_700M,
Qwen3_0_6B_Pro,
Qwen3_1_7B_Pro,
FunctionGemma_270M_Pro,
) + customModels

override fun addCustomModel(model: LLModel) {
customModels = customModels + model
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ import io.github.lemcoder.koog.edge.cactus.internal.converter.koogToCactusMessag
import io.github.lemcoder.koog.edge.cactus.internal.converter.koogToCactusToolConverter
import io.github.lemcoder.koog.edge.log.KoogEdgeLog
import io.github.lemcoder.koog.edge.provider.LocalLLMProvider
import kotlinx.datetime.Clock
import kotlin.time.Clock

class CactusLocalLLMClient(private val modelLoader: LocalModelLoader<CactusLM?>) : LLMClient {
class CactusLocalLLMClient(private val modelLoader: LocalModelLoader<CactusLM?>) : LLMClient() {
override suspend fun execute(
prompt: Prompt,
model: LLModel,
tools: List<ToolDescriptor>,
): List<Message.Response> {
KoogEdgeLog.w { "Executing prompt: $prompt with tools: $tools and model: $model" }
require(model.capabilities.contains(LLMCapability.Completion)) {
require(model.capabilities?.contains(LLMCapability.Completion) == true) {
"Model ${model.id} does not support chat completions"
}
require(model.capabilities.contains(LLMCapability.Tools) || tools.isEmpty()) {
require(model.capabilities?.contains(LLMCapability.Tools) == true || tools.isEmpty()) {
"Model ${model.id} does not support tools"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import ai.koog.prompt.message.ResponseMetaInfo
import com.cactus.ChatMessage
import com.cactus.ToolCall
import io.github.lemcoder.koog.edge.util.Converter
import kotlin.time.Clock
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlinx.datetime.Clock
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.github.lemcoder.koog.edge.LocalModel
import io.github.lemcoder.koog.edge.provider.LocalLLMProvider

sealed interface LeapModels : LocalModel {
data object Chat : LocalModel {
data object Chat : LeapModels {
val LFM2_1_2B_Tool =
LLModel(
provider = LocalLLMProvider,
Expand All @@ -30,6 +30,13 @@ sealed interface LeapModels : LocalModel {
capabilities = listOf(LLMCapability.Tools, LLMCapability.Completion),
contextLength = 32_768,
)
private var customModels: List<LLModel> = emptyList()
override val models: List<LLModel>
get() = listOf(LFM2_1_2B_Tool, LFM2_1_2B_Instruct, LFM2_1_2B_Thinking) + customModels

override fun addCustomModel(model: LLModel) {
customModels = customModels + model
}
}
}

Expand Down
Loading