logging: kotlin-logging 3.0.5 + logback-classic + AGENTIK_LOG_LEVEL env
Заменил все System.err.println / println на структурное логирование
(kotlin-logging, пакет mu) — теперь логи идут с timestamp/level/thread/logger.
* gradle/libs.versions.toml: kotlin-logging = "3.0.5" (в прокси доступна
только эта версия; новые 7.x пока не подтянуты), logback-classic = "1.5.18".
* standalone/build.gradle.kts: implementation(libs.kotlin.logging) +
implementation(libs.logback.classic) в jvmMain.
* standalone/src/jvmMain/resources/logback.xml: консольный appender,
pattern с timestamp/level/thread/logger, level управляется через
${AGENTIK_LOG_LEVEL:-INFO} (env override на старте JVM), уровни
io.netty/ai.onnxruntime уведены в WARN чтобы не забивать канал.
* Заменены все System.err.println в: ChatConversation (14 callsites),
Curator (2), McpRegistry (5), McpConfig (2), Main (2). Startup banner
в Main оставлен на println — это user-facing output, не log.
* Tests: 288 зелёных (только замена log-вызовов, без изменения семантики).
Smoke: `04:53:49.313 INFO [DefaultDispatcher-worker-4] p.b.a.s.agent.memory.Curator - started (interval=1d, maxAge=90d, maxUseCount=0)`
подтверждает структурный лог вместо println. AGENTIK_LOG_LEVEL=DEBUG работает.
This commit is contained in:
@@ -11,6 +11,8 @@ sqldelight = "2.3.2"
|
|||||||
shadow = "8.3.5"
|
shadow = "8.3.5"
|
||||||
jvector = "3.0.6"
|
jvector = "3.0.6"
|
||||||
text-embedding-kmp = "3.0.0-SNAPSHOT"
|
text-embedding-kmp = "3.0.0-SNAPSHOT"
|
||||||
|
kotlin-logging = "3.0.5"
|
||||||
|
logback = "1.5.18"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
@@ -73,3 +75,7 @@ jvector = { module = "io.github.jbellis:jvector", version.ref = "jvector" }
|
|||||||
# Артефакты публикуются под именами `-jvm` (KMP convention для JVM-таргета).
|
# Артефакты публикуются под именами `-jvm` (KMP convention для JVM-таргета).
|
||||||
text-embedding-api = { module = "pw.binom.ai.embeddingtext:api-jvm", version.ref = "text-embedding-kmp" }
|
text-embedding-api = { module = "pw.binom.ai.embeddingtext:api-jvm", version.ref = "text-embedding-kmp" }
|
||||||
text-embedding-siglip = { module = "pw.binom.ai.embeddingtext:siglip-jvm", version.ref = "text-embedding-kmp" }
|
text-embedding-siglip = { module = "pw.binom.ai.embeddingtext:siglip-jvm", version.ref = "text-embedding-kmp" }
|
||||||
|
|
||||||
|
# --- Логирование: kotlin-logging (тонкая обёртка над slf4j-api) + logback-classic (binding). ---
|
||||||
|
kotlin-logging = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlin-logging" }
|
||||||
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ kotlin {
|
|||||||
implementation(libs.ktor.client.cio)
|
implementation(libs.ktor.client.cio)
|
||||||
implementation(libs.ktor.client.content.negotiation)
|
implementation(libs.ktor.client.content.negotiation)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
|
||||||
|
// Логирование: kotlin-logging (тонкая обёртка slf4j-api) + logback-classic
|
||||||
|
// (binding для JVM; без него slf4j-api NOP-логирует и не падает).
|
||||||
|
implementation(libs.kotlin.logging)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
}
|
}
|
||||||
|
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package pw.binom.agentik.standalone
|
package pw.binom.agentik.standalone
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
|
||||||
|
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.response.respondText
|
import io.ktor.server.response.respondText
|
||||||
@@ -47,6 +50,7 @@ import java.io.File
|
|||||||
* - Skills: AGENTIK_SKILLS_DIR=<path> (папка с SKILL.md / *.yaml)
|
* - Skills: AGENTIK_SKILLS_DIR=<path> (папка с SKILL.md / *.yaml)
|
||||||
* - AGENTIK_SYSTEM_PROMPT (default: встроенный `Ты полезный ассистент...`)
|
* - AGENTIK_SYSTEM_PROMPT (default: встроенный `Ты полезный ассистент...`)
|
||||||
*/
|
*/
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
fun main() {
|
fun main() {
|
||||||
val config = AgentikConfig.fromEnv()
|
val config = AgentikConfig.fromEnv()
|
||||||
|
|
||||||
@@ -55,7 +59,7 @@ fun main() {
|
|||||||
val mcpRegistry = McpRegistry.fromConfig(config.mcp)
|
val mcpRegistry = McpRegistry.fromConfig(config.mcp)
|
||||||
val skills = config.skillsDir?.let { dir ->
|
val skills = config.skillsDir?.let { dir ->
|
||||||
val result = SkillLoader.loadDirectory(File(dir))
|
val result = SkillLoader.loadDirectory(File(dir))
|
||||||
result.errors.forEach { System.err.println("[agentik] skill '${it.path}': ${it.message}") }
|
result.errors.forEach { log.warn { "skill '${it.path}': ${it.message}" } }
|
||||||
result.catalog
|
result.catalog
|
||||||
} ?: SkillCatalog.EMPTY
|
} ?: SkillCatalog.EMPTY
|
||||||
|
|
||||||
@@ -65,7 +69,7 @@ fun main() {
|
|||||||
val soulBody = config.soulPath?.let { path ->
|
val soulBody = config.soulPath?.let { path ->
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (!file.exists() || !file.isFile) {
|
if (!file.exists() || !file.isFile) {
|
||||||
System.err.println("[agentik] SOUL file not found: $path")
|
log.warn { "SOUL file not found: $path" }
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
file.readText(Charsets.UTF_8)
|
file.readText(Charsets.UTF_8)
|
||||||
|
|||||||
+18
-14
@@ -1,5 +1,7 @@
|
|||||||
package pw.binom.agentik.standalone.agent
|
package pw.binom.agentik.standalone.agent
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -223,7 +225,7 @@ class ChatConversation(
|
|||||||
when (c) {
|
when (c) {
|
||||||
is Content.Text -> LiteContentPart.Text(c.body)
|
is Content.Text -> LiteContentPart.Text(c.body)
|
||||||
is Content.Image -> {
|
is Content.Image -> {
|
||||||
System.err.println("[agentik] dropping image input (v1 text-only): mime=${c.mime}, ${c.data.size} bytes")
|
log.warn { "dropping image input (v1 text-only): mime=${c.mime}, ${c.data.size} bytes" }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +290,7 @@ class ChatConversation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loopGuard >= MAX_TOOL_LOOPS) {
|
if (loopGuard >= MAX_TOOL_LOOPS) {
|
||||||
System.err.println("[agentik] tool loop hit MAX_TOOL_LOOPS=$MAX_TOOL_LOOPS for $id — bailing")
|
log.warn { "tool loop hit MAX_TOOL_LOOPS=$MAX_TOOL_LOOPS for $id — bailing" }
|
||||||
}
|
}
|
||||||
|
|
||||||
val assistantId = newId("msg")
|
val assistantId = newId("msg")
|
||||||
@@ -337,7 +339,7 @@ class ChatConversation(
|
|||||||
val notes = try {
|
val notes = try {
|
||||||
prefetcher.prefetch(userText, topK = 10)
|
prefetcher.prefetch(userText, topK = 10)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] memory prefetch failed: ${e.message}")
|
log.warn(e) { "memory prefetch failed: ${e.message}" }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (notes.isEmpty()) return null
|
if (notes.isEmpty()) return null
|
||||||
@@ -427,7 +429,7 @@ class ChatConversation(
|
|||||||
}
|
}
|
||||||
if (pendingUser != null) paired.add(pendingUser)
|
if (pendingUser != null) paired.add(pendingUser)
|
||||||
if (paired.isEmpty()) {
|
if (paired.isEmpty()) {
|
||||||
System.err.println("[agentik] compactPreTurn: nothing to compact for $id")
|
log.info { "compactPreTurn: nothing to compact for $id" }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +438,7 @@ class ChatConversation(
|
|||||||
} catch (e: kotlinx.coroutines.CancellationException) {
|
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] context summarization failed for $id: ${e.message}")
|
log.warn(e) { "context summarization failed for $id: ${e.message}" }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (summaryText.isBlank()) return
|
if (summaryText.isBlank()) return
|
||||||
@@ -457,16 +459,16 @@ class ChatConversation(
|
|||||||
for (n in decision.toSave) {
|
for (n in decision.toSave) {
|
||||||
val note = materializeReviewNote(n, conversationId = null)
|
val note = materializeReviewNote(n, conversationId = null)
|
||||||
runCatching { store.upsert(note) }
|
runCatching { store.upsert(note) }
|
||||||
.onFailure { System.err.println("[agentik] pre-compaction upsert failed: ${it.message}") }
|
.onFailure { log.warn(it) { "pre-compaction upsert failed: ${it.message}" } }
|
||||||
}
|
}
|
||||||
for (delId in decision.toDelete) {
|
for (delId in decision.toDelete) {
|
||||||
runCatching { store.delete(delId) }
|
runCatching { store.delete(delId) }
|
||||||
.onFailure { System.err.println("[agentik] pre-compaction delete failed: ${it.message}") }
|
.onFailure { log.warn(it) { "pre-compaction delete failed: ${it.message}" } }
|
||||||
}
|
}
|
||||||
} catch (e: kotlinx.coroutines.CancellationException) {
|
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] pre-compaction review failed for $id: ${e.message}")
|
log.warn(e) { "pre-compaction review failed for $id: ${e.message}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +487,7 @@ class ChatConversation(
|
|||||||
toolsChars = toolsChars,
|
toolsChars = toolsChars,
|
||||||
)
|
)
|
||||||
if (after.toDouble() / window >= compressionThreshold) {
|
if (after.toDouble() / window >= compressionThreshold) {
|
||||||
System.err.println("[agentik] compactPreTurn: still over threshold for $id (estimated=$after, window=$window, threshold=$compressionThreshold). Consider raising contextWindow or lowering threshold.")
|
log.warn { "compactPreTurn: still over threshold for $id (estimated=$after, window=$window, threshold=$compressionThreshold). Consider raising contextWindow or lowering threshold." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,14 +545,14 @@ class ChatConversation(
|
|||||||
for (n in decision.toSave) {
|
for (n in decision.toSave) {
|
||||||
val note = materializeReviewNote(n, conversationId = null)
|
val note = materializeReviewNote(n, conversationId = null)
|
||||||
runCatching { store.upsert(note) }
|
runCatching { store.upsert(note) }
|
||||||
.onFailure { System.err.println("[agentik] review upsert failed: ${it.message}") }
|
.onFailure { log.warn(it) { "review upsert failed: ${it.message}" } }
|
||||||
}
|
}
|
||||||
for (id in decision.toDelete) {
|
for (id in decision.toDelete) {
|
||||||
runCatching { store.delete(id) }
|
runCatching { store.delete(id) }
|
||||||
.onFailure { System.err.println("[agentik] review delete failed: ${it.message}") }
|
.onFailure { log.warn(it) { "review delete failed: ${it.message}" } }
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] review failed for $convId: ${e.message}")
|
log.warn(e) { "review failed for $convId: ${e.message}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,13 +584,13 @@ class ChatConversation(
|
|||||||
|
|
||||||
val tool = toolsByName[call.name]
|
val tool = toolsByName[call.name]
|
||||||
val resultText: String = if (tool == null) {
|
val resultText: String = if (tool == null) {
|
||||||
System.err.println("[agentik] tool '${call.name}' requested but not registered")
|
log.warn { "tool '${call.name}' requested but not registered" }
|
||||||
"[tool not found: ${call.name}]"
|
"[tool not found: ${call.name}]"
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
tool.tool.invoke(argsJson).ifBlank { "<empty result>" }
|
tool.tool.invoke(argsJson).ifBlank { "<empty result>" }
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] tool '${call.name}' threw: ${e.message}")
|
log.warn(e) { "tool '${call.name}' threw: ${e.message}" }
|
||||||
"[tool error: ${e.message ?: e.javaClass.simpleName}]"
|
"[tool error: ${e.message ?: e.javaClass.simpleName}]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -707,6 +709,8 @@ class ChatConversation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
private const val MAX_TOOL_LOOPS = 16
|
private const val MAX_TOOL_LOOPS = 16
|
||||||
/** Сколько последних ходов оставляем нетронутыми при compaction. */
|
/** Сколько последних ходов оставляем нетронутыми при compaction. */
|
||||||
private const val KEEP_RECENT_TURNS = 4
|
private const val KEEP_RECENT_TURNS = 4
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package pw.binom.agentik.standalone.agent.memory
|
package pw.binom.agentik.standalone.agent.memory
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -32,12 +34,13 @@ class Curator(
|
|||||||
private val clock: Clock = Clock.System,
|
private val clock: Clock = Clock.System,
|
||||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
|
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
|
||||||
) {
|
) {
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (job != null) return
|
if (job != null) return
|
||||||
job = scope.launch {
|
job = scope.launch {
|
||||||
println("[Curator] started (interval=$interval, maxAge=$maxAge, maxUseCount=$maxUseCount)")
|
log.info { "started (interval=$interval, maxAge=$maxAge, maxUseCount=$maxUseCount)" }
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
runPass()
|
runPass()
|
||||||
delay(interval)
|
delay(interval)
|
||||||
@@ -53,7 +56,7 @@ class Curator(
|
|||||||
now = clock.now(),
|
now = clock.now(),
|
||||||
)
|
)
|
||||||
if (archived > 0) {
|
if (archived > 0) {
|
||||||
println("[Curator] archived $archived stale notes (maxAge=$maxAge, maxUseCount=$maxUseCount)")
|
log.info { "archived $archived stale notes (maxAge=$maxAge, maxUseCount=$maxUseCount)" }
|
||||||
}
|
}
|
||||||
return archived
|
return archived
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package pw.binom.agentik.standalone.mcp
|
package pw.binom.agentik.standalone.mcp
|
||||||
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -57,13 +58,14 @@ data class McpConfig(
|
|||||||
val isEmpty: Boolean get() = servers.isEmpty()
|
val isEmpty: Boolean get() = servers.isEmpty()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = mu.KotlinLogging.logger {}
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
fun fromEnv(env: (String) -> String? = System::getenv): McpConfig {
|
fun fromEnv(env: (String) -> String? = System::getenv): McpConfig {
|
||||||
val path = env("AGENTIK_MCP_CONFIG")?.takeIf { it.isNotBlank() } ?: return empty()
|
val path = env("AGENTIK_MCP_CONFIG")?.takeIf { it.isNotBlank() } ?: return empty()
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
System.err.println("[agentik] AGENTIK_MCP_CONFIG points to missing file: $path")
|
log.warn { "AGENTIK_MCP_CONFIG points to missing file: $path" }
|
||||||
return empty()
|
return empty()
|
||||||
}
|
}
|
||||||
return fromJson(file.readText())
|
return fromJson(file.readText())
|
||||||
@@ -96,7 +98,7 @@ data class McpConfig(
|
|||||||
?: emptyMap()
|
?: emptyMap()
|
||||||
return McpServerSpec.Stdio(name = name, command = command, args = args, env = env)
|
return McpServerSpec.Stdio(name = name, command = command, args = args, env = env)
|
||||||
}
|
}
|
||||||
System.err.println("[agentik] MCP server '$name' has neither 'url' nor 'command' — skipped")
|
log.warn { "MCP server '$name' has neither 'url' nor 'command' — skipped" }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package pw.binom.agentik.standalone.mcp
|
package pw.binom.agentik.standalone.mcp
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.client.plugins.defaultRequest
|
import io.ktor.client.plugins.defaultRequest
|
||||||
@@ -42,6 +44,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
*
|
*
|
||||||
* [close] убивает stdio-процессы и закрывает HTTP-клиент.
|
* [close] убивает stdio-процессы и закрывает HTTP-клиент.
|
||||||
*/
|
*/
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
class McpRegistry(
|
class McpRegistry(
|
||||||
private val servers: List<McpServerSpec>,
|
private val servers: List<McpServerSpec>,
|
||||||
private val httpClient: HttpClient = defaultHttpClient(),
|
private val httpClient: HttpClient = defaultHttpClient(),
|
||||||
@@ -72,10 +75,10 @@ class McpRegistry(
|
|||||||
val server = connectOne(spec)
|
val server = connectOne(spec)
|
||||||
connected[spec.name] = server
|
connected[spec.name] = server
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
System.err.println("[agentik] MCP server '${spec.name}' failed to connect: ${e.message}")
|
log.warn { "MCP server '${spec.name}' failed to connect: ${e.message}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.err.println("[agentik] MCP registry: ${connected.size}/${servers.size} servers connected, ${allTools.size} tools total")
|
log.warn { "MCP registry: ${connected.size}/${servers.size} servers connected, ${allTools.size} tools total" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ class McpRegistry(
|
|||||||
val transport: Transport = when (spec) {
|
val transport: Transport = when (spec) {
|
||||||
is McpServerSpec.Stdio -> {
|
is McpServerSpec.Stdio -> {
|
||||||
val cmd = (listOf(spec.command) + spec.args).joinToString(" ")
|
val cmd = (listOf(spec.command) + spec.args).joinToString(" ")
|
||||||
System.err.println("[agentik] MCP stdio '$spec.name': $cmd")
|
log.warn { "MCP stdio '$spec.name': $cmd" }
|
||||||
val pb = ProcessBuilder(buildList { add(spec.command); addAll(spec.args) })
|
val pb = ProcessBuilder(buildList { add(spec.command); addAll(spec.args) })
|
||||||
.redirectErrorStream(false)
|
.redirectErrorStream(false)
|
||||||
spec.env.forEach { (k, v) -> pb.environment()[k] = v }
|
spec.env.forEach { (k, v) -> pb.environment()[k] = v }
|
||||||
@@ -97,7 +100,7 @@ class McpRegistry(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is McpServerSpec.Http -> {
|
is McpServerSpec.Http -> {
|
||||||
System.err.println("[agentik] MCP http '$spec.name': ${spec.url}")
|
log.warn { "MCP http '$spec.name': ${spec.url}" }
|
||||||
StreamableHttpClientTransport(
|
StreamableHttpClientTransport(
|
||||||
client = httpClient.config {
|
client = httpClient.config {
|
||||||
if (spec.headers.isNotEmpty()) {
|
if (spec.headers.isNotEmpty()) {
|
||||||
@@ -230,7 +233,7 @@ internal class McpLiteToolAdapter(
|
|||||||
val parsed = json.parseToJsonElement(raw)
|
val parsed = json.parseToJsonElement(raw)
|
||||||
if (parsed !is JsonObject) emptyMap() else parsed.toAnyMap()
|
if (parsed !is JsonObject) emptyMap() else parsed.toAnyMap()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
System.err.println("[agentik] MCP tool '$toolName' got invalid args JSON: ${e.message}")
|
log.warn { "MCP tool '$toolName' got invalid args JSON: ${e.message}" }
|
||||||
emptyMap()
|
emptyMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Logback-конфиг для standalone.
|
||||||
|
Уровень управляется env var AGENTIK_LOG_LEVEL (default INFO).
|
||||||
|
Формат: timestamp [level] [thread] logger — message
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Default уровень — INFO. Можно перебить через env: AGENTIK_LOG_LEVEL=DEBUG -->
|
||||||
|
<root level="${AGENTIK_LOG_LEVEL:-INFO}">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!-- Шумные библиотеки уводим в WARN. -->
|
||||||
|
<logger name="io.netty" level="WARN"/>
|
||||||
|
<logger name="io.ktor" level="INFO"/>
|
||||||
|
<logger name="ai.onnxruntime" level="WARN"/>
|
||||||
|
</configuration>
|
||||||
Reference in New Issue
Block a user