Stephan Schröder
12/08/2025, 3:09 PMOpenRouterModels is an object, so OpenRouterModels can denote a type or an instance)
import ai.koog.prompt.llm.LLModel
import ai.koog.prompt.executor.clients.openrouter.OpenRouterModels
import kotlin.reflect.full.memberProperties
private val openRouterModelsByName: Map<String, LLModel> = OpenRouterModels::class.memberProperties
.filter { it.returnType.classifier == LLModel::class }
.associate { it.name to it.get(OpenRouterModels) as LLModel }
and I would expect that the following code is equivalent (it's another way of doing the filtering):
private val openRouterModelsByName: Map<String, LLModel> = OpenRouterModels::class.memberProperties
.mapNotNull {
val name: String = it.name
val model: LLModel? = it.get(OpenRouterModels) as? LLModel
model?.let { name to model }
}.toMap()
alas the it.get(OpenRouterModels) fails 🤯with Caused by: java.lang.IllegalArgumentException: Callable expects 0 arguments, but 1 were provided. even though IntelliJ shows no problem with the code.
OpenRouterModels is defined in "ai.koogkoog agents0.5.3" but I don't expect this issue to be linked to koog.CLOVIS
12/08/2025, 3:15 PMprivate val openRouterModelsByName: Map<String, LLModel> = OpenRouterModels::class.memberProperties
.mapNotNull {
if (it.returnType.classifier != LLModel::class)
return@mapNotNull null
val name: String = it.name
val model: LLModel? = it.get(OpenRouterModels) as? LLModel
model?.let { name to model }
}.toMap()CLOVIS
12/08/2025, 3:16 PMname of the field that complains and see what its signature is?Youssef Shoaib [MOD]
12/08/2025, 3:21 PMmemberProperty is somehow not actually a member... I think you could check if the underlying java function is static or not.
The first debugging step I'd do is just print all the member propertiesStephan Schröder
12/08/2025, 3:22 PMprivate val additionalCapabilities: List<LLMCapability> = listOf(
LLMCapability.Schema.JSON.Standard,
LLMCapability.ToolChoice
)
which is the first private property that is iterated over, but that isn't an explaination to way get shouldn't have a single parameter (the instance that this property is to be extracted from) !?Stephan Schröder
12/08/2025, 3:24 PM@SinceKotlin("1.1")
val <T : Any> KClass<T>.memberProperties: Collection<KProperty1<T, *>>
get() = (this as KClassImpl<T>).data.value.allNonStaticMembers.filter { it.isNotExtension && it is KProperty1<*, *> } as Collection<KProperty1<T, *>>Youssef Shoaib [MOD]
12/08/2025, 3:24 PMStephan Schröder
12/08/2025, 3:33 PMval OpenRouterModels.modelsByName: Map<String, LLModel>
get() {
return runCatching {
openRouterModelsByName
}.getOrElse{e->
e.printStackTrace(System.out)
throw e
}
}
after making the computation lazy
private val openRouterModelsByName: Map<String, LLModel> by lazy {
OpenRouterModels::class.memberProperties
.mapNotNull {
val name: String = it.name
println("name: $name")
val model: LLModel? = it.get(OpenRouterModels) as? LLModel
model?.let { name to model }
}.toMap().also { map ->
require(map.isNotEmpty()) { "No LLModels were found in OpenRouterModels." }
}
}
but still no stacktrace is printed 🤯🤷♂️Youssef Shoaib [MOD]
12/08/2025, 3:36 PMYoussef Shoaib [MOD]
12/08/2025, 3:36 PMadditionalCapabilities marked @JvmField? If so, then it's https://youtrack.jetbrains.com/issue/KT-55872/Reflection-redundant-instanceParameter-on-property-annotated-with-JvmField
As a workaround, I'd add a try catch around the get call that checks for an IAE with that specific message, and then recover by doing a call with no argumentsStephan Schröder
12/08/2025, 3:39 PMYoussef Shoaib [MOD]
12/08/2025, 3:40 PMrunCatching { it.get(OpenRoutersModels) }.getOrElse { if (it is IllegalArgumentException && it.message == "Callable expects 0 arguments, but 1 were provided.") it.get() else throw it }Stephan Schröder
12/08/2025, 3:45 PMStephan Schröder
12/08/2025, 3:46 PMYoussef Shoaib [MOD]
12/08/2025, 3:47 PMconst and @JvmField properties, but some mockk issues mention something similar for private properties. Either way, it's a pretty solid workaroundCLOVIS
12/08/2025, 3:47 PMAs a workaround, I'd add a try catch around theHonestly, in this situation, I think keeping the initial code is better. Testing if the declared output is a given type is much cheaper than calling all the properties (which would trigger lazy initialization etc) when you're only interested by the result of a few of themcall that checks for an IAE with that specific message, and then recover by doing a call with no argumentsget
Youssef Shoaib [MOD]
12/08/2025, 3:49 PMOpenRoutersModels adds a private or @JvmField property in the future, it'll retrigger the bug...Stephan Schröder
12/08/2025, 3:57 PMprivate val openRouterModelsByName: Map<String, LLModel> by lazy {
OpenRouterModels::class.memberProperties
.filter { it.returnType.classifier == LLModel::class }
.associate { it.name to it.get(OpenRouterModels) as LLModel }
}
I've added a link to my example to the bug report you mentioned 👍Youssef Shoaib [MOD]
12/08/2025, 3:59 PMOpenRouterModels gains a new private or @JvmField property. At the very least, I'd filter out private properties, since you likely don't want them anyway (and they'll fail, I think, since you'll be trying to access a JVM Field without calling setAccessible on it first)Stephan Schröder
12/08/2025, 4:03 PMisAccessible is correct for that, isn't it?
.filter { it.isAccessible && it.returnType.classifier == LLModel::class }Stephan Schröder
12/08/2025, 4:04 PM.filter { it.visibility == KVisibility.PUBLIC && it.returnType.classifier == LLModel::class }Stephan Schröder
12/08/2025, 4:11 PMprivate val openRouterModelsByName: Map<String, LLModel> = OpenRouterModels::class.memberProperties
.mapNotNull {
if (it.visibility == KVisibility.PUBLIC) {
val name: String = it.name
val model: LLModel? = it.get(OpenRouterModels) as? LLModel
model?.let { name to model }
} else null
}.toMap()
now works as well 👍 even though I prefer the version with associate .