diff --git a/android/build.gradle.kts b/android/build.gradle.kts index a4a6b7c..988133e 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/calculator/CalculatorAgentProvider.kt b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/calculator/CalculatorAgentProvider.kt index 45b6d59..27a8e07 100644 --- a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/calculator/CalculatorAgentProvider.kt +++ b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/calculator/CalculatorAgentProvider.kt @@ -3,9 +3,6 @@ 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 @@ -13,8 +10,8 @@ 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 @@ -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, diff --git a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/chat/ChatAgentProvider.kt b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/chat/ChatAgentProvider.kt index 6c91e30..a69b7bc 100644 --- a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/chat/ChatAgentProvider.kt +++ b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/chat/ChatAgentProvider.kt @@ -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 diff --git a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/common/Common.kt b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/common/Common.kt index 4c9e477..9ea1e8f 100644 --- a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/common/Common.kt +++ b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/common/Common.kt @@ -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 diff --git a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherAgentProvider.kt b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherAgentProvider.kt index 7f927b6..b890aa2 100644 --- a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherAgentProvider.kt +++ b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherAgentProvider.kt @@ -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 @@ -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, diff --git a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherTools.kt b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherTools.kt index 5e6f1ae..6c069bc 100644 --- a/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherTools.kt +++ b/android/src/main/kotlin/io/github/lemcoder/koogedge/agents/weather/WeatherTools.kt @@ -2,15 +2,12 @@ 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 @@ -18,7 +15,7 @@ import kotlinx.serialization.Serializable 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 @@ -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, ) } @@ -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, @@ -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()) { @@ -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) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70edc79..e5765ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 63bc03a..0079868 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/koog-edge/build.gradle.kts b/koog-edge/build.gradle.kts index d2d08a2..2a05a1f 100644 --- a/koog-edge/build.gradle.kts +++ b/koog-edge/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "io.github.lemcoder" -version = "0.0.3" +version = "0.0.4" kotlin { jvmToolchain(17) diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/CactusModels.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/CactusModels.kt index 50a7cc7..e161b1d 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/CactusModels.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/CactusModels.kt @@ -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, @@ -140,6 +140,26 @@ sealed interface CactusModels : LocalModel { capabilities = listOf(LLMCapability.Tools, LLMCapability.Completion), contextLength = 16_384, ) + private var customModels: List = emptyList() + override val models: List + 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 + } } } diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/CactusLocalLLMClient.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/CactusLocalLLMClient.kt index 2929a4f..ea1c50b 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/CactusLocalLLMClient.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/CactusLocalLLMClient.kt @@ -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) : LLMClient { +class CactusLocalLLMClient(private val modelLoader: LocalModelLoader) : LLMClient() { override suspend fun execute( prompt: Prompt, model: LLModel, tools: List, ): List { 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" } diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/converter/CactusMessageConverter.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/converter/CactusMessageConverter.kt index afe5874..55ade9b 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/converter/CactusMessageConverter.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/cactus/internal/converter/CactusMessageConverter.kt @@ -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 diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/LeapModels.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/LeapModels.kt index 88b286f..f8b87c9 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/LeapModels.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/LeapModels.kt @@ -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, @@ -30,6 +30,13 @@ sealed interface LeapModels : LocalModel { capabilities = listOf(LLMCapability.Tools, LLMCapability.Completion), contextLength = 32_768, ) + private var customModels: List = emptyList() + override val models: List + get() = listOf(LFM2_1_2B_Tool, LFM2_1_2B_Instruct, LFM2_1_2B_Thinking) + customModels + + override fun addCustomModel(model: LLModel) { + customModels = customModels + model + } } } diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapLocalLLMClient.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapLocalLLMClient.kt index 29f1c33..4413a08 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapLocalLLMClient.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapLocalLLMClient.kt @@ -7,6 +7,7 @@ import ai.koog.prompt.executor.clients.LLMClient import ai.koog.prompt.llm.LLMCapability import ai.koog.prompt.llm.LLMProvider import ai.koog.prompt.llm.LLModel +import ai.koog.prompt.message.ContentPart import ai.koog.prompt.message.Message import ai.koog.prompt.message.ResponseMetaInfo import ai.koog.prompt.streaming.StreamFrame @@ -17,23 +18,23 @@ import io.github.lemcoder.koog.edge.leap.internal.converter.leapFunctionConverte import io.github.lemcoder.koog.edge.leap.internal.converter.messageResponseToStreamFrameConverter import io.github.lemcoder.koog.edge.log.KoogEdgeLog import io.github.lemcoder.koog.edge.provider.LocalLLMProvider +import kotlin.time.Clock import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow -import kotlinx.datetime.Clock -internal open class LeapLocalLLMClient(private val modelLoader: LeapModelLoader) : LLMClient { +internal class LeapLocalLLMClient(private val modelLoader: LeapModelLoader) : LLMClient() { override suspend fun execute( prompt: Prompt, model: LLModel, tools: List, ): List { 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" } @@ -74,10 +75,23 @@ internal open class LeapLocalLLMClient(private val modelLoader: LeapModelLoader) frames.forEach { frame -> KoogEdgeLog.warning("Received frame: $frame") when (frame) { - is StreamFrame.Append -> responseText.append(frame.text) is StreamFrame.End -> finishReason = frame.finishReason - is StreamFrame.ToolCall -> - toolCalls.add(frame.toMessageResponse() as Message.Tool.Call) + is StreamFrame.TextComplete -> responseText.append(frame.text) + is StreamFrame.TextDelta -> responseText.append(frame.text) + is StreamFrame.ToolCallComplete -> { + toolCalls += + Message.Tool.Call( + id = frame.id, + tool = frame.name, + content = frame.content, + metaInfo = ResponseMetaInfo.create(Clock.System), + ) + } + is StreamFrame.ReasoningComplete, + is StreamFrame.ReasoningDelta, + is StreamFrame.ToolCallDelta -> { + // Ignored for now in non-streaming execute() aggregation. + } } } } @@ -113,10 +127,10 @@ internal open class LeapLocalLLMClient(private val modelLoader: LeapModelLoader) tools: List, ): Flow = flow { 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" } val leapLLModel = getLeapLLModelById(model.id) @@ -161,18 +175,37 @@ internal open class LeapLocalLLMClient(private val modelLoader: LeapModelLoader) private fun StreamFrame.toMessageResponse(): Message.Response { val metaInfo = ResponseMetaInfo(timestamp = Clock.System.now()) return when (this) { - is StreamFrame.Append -> - Message.Assistant(content = this.text, metaInfo = metaInfo, finishReason = "") + is StreamFrame.ReasoningComplete -> + Message.Reasoning( + encrypted = encrypted, + parts = text.map { ContentPart.Text(it) }, + summary = summary?.map { ContentPart.Text(it) }, + metaInfo = metaInfo, + ) - is StreamFrame.End -> - Message.Assistant(content = "", metaInfo = metaInfo, finishReason = this.finishReason) + is StreamFrame.TextComplete -> Message.Assistant(content = text, metaInfo = metaInfo) + + is StreamFrame.ToolCallComplete -> + Message.Tool.Call(id = id, tool = name, content = content, metaInfo = metaInfo) + + is StreamFrame.ReasoningDelta -> + Message.Reasoning( + parts = listOf(ContentPart.Text(text.orEmpty())), + summary = summary?.let { listOf(ContentPart.Text(it)) }, + metaInfo = metaInfo, + ) + + is StreamFrame.TextDelta -> Message.Assistant(content = text, metaInfo = metaInfo) - is StreamFrame.ToolCall -> + is StreamFrame.ToolCallDelta -> Message.Tool.Call( - id = this.id, - tool = this.name, - content = this.content, + id = id, + tool = name.orEmpty(), + content = content.orEmpty(), metaInfo = metaInfo, ) + + is StreamFrame.End -> + Message.Assistant(content = "", metaInfo = metaInfo, finishReason = finishReason) } } diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapModelLoader.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapModelLoader.kt index 4f4dadc..d38c47c 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapModelLoader.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/LeapModelLoader.kt @@ -15,6 +15,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem internal class LeapModelLoader( private val modelsPath: String, @@ -29,6 +31,10 @@ internal class LeapModelLoader( override suspend fun loadModel(model: LLModel): ModelRunner? = withContext(Dispatchers.IO) { mutex.withLock { + val modelPath = "${modelsPath}/${model.id}" + KoogEdgeLog.w { "Loading model ${model.id} from path: $model at $modelPath" } + SystemFileSystem.delete(Path(modelPath), false) + if (loadingJob?.isActive == true) { throw IllegalStateException("A model is already loading") } @@ -37,10 +43,11 @@ internal class LeapModelLoader( try { currentRunner = downloader.loadModel( - modelSlug = model.id, + modelName = model.id, quantizationSlug = - "Q_4_0", // Load 4-bit quantized models by default + "Q4_K_M", // Load 4-bit quantized models by default modelLoadingOptions = options, + forceDownload = true, ) } catch (e: LeapModelLoadingException) { KoogEdgeLog.error("Error loading model: ${e.message}", e) diff --git a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/converter/LeapMessageConverter.kt b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/converter/LeapMessageConverter.kt index 9c2eabc..52d3f56 100644 --- a/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/converter/LeapMessageConverter.kt +++ b/koog-edge/src/commonMain/kotlin/io/github/lemcoder/koog/edge/leap/internal/converter/LeapMessageConverter.kt @@ -39,13 +39,13 @@ internal val koogToLeapMessageConverter = internal val messageResponseToStreamFrameConverter = Converter> { response -> when (response) { - is MessageResponse.Chunk -> listOf(Append(text = response.text)) + is MessageResponse.Chunk -> listOf(TextComplete(text = response.text)) is MessageResponse.Complete -> listOf(End()) is MessageResponse.FunctionCalls -> response.functionCalls.map { firstCall -> - ToolCall( + ToolCallComplete( id = null, name = firstCall.name, content = firstCall.arguments.toJsonObjectString(),