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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.askimo.core.context.AppContext
import io.askimo.core.context.ExecutionMode
import io.askimo.core.telemetry.TelemetryChatModelListener
import io.askimo.core.util.ProxyUtil
import io.askimo.core.util.withLoggingIfDebug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -155,7 +156,7 @@ abstract class OpenAiCompatibleChatModelFactory<T> : ChatModelFactory<T>
ProxyUtil.configureProxy(
HttpClient.newBuilder().version(httpVersion()),
baseUrl,
),
).withLoggingIfDebug(),
).readTimeout(Duration.ofSeconds(AppConfig.models.timeouts.defaultModelTimeoutSeconds))
.connectTimeout(Duration.ofSeconds(AppConfig.models.timeouts.defaultModelTimeoutSeconds))

Expand Down Expand Up @@ -247,6 +248,7 @@ abstract class OpenAiCompatibleChatModelFactory<T> : ChatModelFactory<T>
val reasoningLevel = ModelCapabilitiesCache.getReasoningLevel(getProvider(), settings.defaultModel)
if (supportsThinking && reasoningLevel.isEnabled) {
reasoningEffort(reasoningLevel.value)
reasoningSummary("detailed")
}
}
.strictTools(true)
Expand Down Expand Up @@ -290,9 +292,6 @@ abstract class OpenAiCompatibleChatModelFactory<T> : ChatModelFactory<T>
.baseUrl(settings.baseUrl)
.apiKey(resolveApiKey(settings))
.modelName(settings.imageModel.ifBlank { AppConfig.models[getProvider()].imageModel })
.logger(log)
.logRequests(log.isDebugEnabled)
.logResponses(log.isTraceEnabled)
.build()

override fun createUtilityClient(settings: T): ChatClient = AiServices.builder(ChatClient::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import io.askimo.core.util.ApiKeyUtils.safeApiKey
import io.askimo.core.util.ProxyUtil
import io.askimo.core.util.appJson
import io.askimo.core.util.httpGet
import io.askimo.core.util.withLoggingIfDebug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -71,7 +72,7 @@ class AnthropicModelFactory : ChatModelFactory<AnthropicSettings> {
chatMemory: ChatMemory?,
): ChatClient {
// Configure HTTP client for thinking probe (probe needs its own builder)
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)

// Probe thinking support once — result is persisted in ModelCapabilitiesCache
Expand Down Expand Up @@ -165,7 +166,7 @@ class AnthropicModelFactory : ChatModelFactory<AnthropicSettings> {
}

override fun createStreamingModel(settings: AnthropicSettings): StreamingChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)
val telemetry = AppContext.getInstance().telemetry

Expand Down Expand Up @@ -210,7 +211,7 @@ class AnthropicModelFactory : ChatModelFactory<AnthropicSettings> {
}

override fun createSecondaryModel(settings: AnthropicSettings): ChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)
return AnthropicChatModel.builder()
.httpClientBuilder(jdkHttpClientBuilder)
Expand All @@ -226,7 +227,7 @@ class AnthropicModelFactory : ChatModelFactory<AnthropicSettings> {
}

override fun createModel(settings: AnthropicSettings): ChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)

return AnthropicChatModel.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import io.askimo.core.providers.sendStreamingMessageWithCallback
import io.askimo.core.telemetry.TelemetryChatModelListener
import io.askimo.core.util.ApiKeyUtils.safeApiKey
import io.askimo.core.util.ProxyUtil
import io.askimo.core.util.withLoggingIfDebug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -69,8 +70,7 @@ class GeminiModelFactory : ChatModelFactory<GeminiSettings> {
executionMode: ExecutionMode,
chatMemory: ChatMemory?,
): ChatClient {
// Configure HTTP client for thinking probe (probe needs its own builder)
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)

// Probe thinking support once — result is persisted in ModelCapabilitiesCache
Expand Down Expand Up @@ -165,13 +165,10 @@ class GeminiModelFactory : ChatModelFactory<GeminiSettings> {
.apiKey(safeApiKey(settings.apiKey))
.baseUrl(settings.baseUrl)
.modelName(settings.imageModel.ifBlank { AppConfig.models[GEMINI].imageModel })
.logger(log)
.logRequests(log.isDebugEnabled)
.logResponses(log.isTraceEnabled)
.build()

override fun createStreamingModel(settings: GeminiSettings): StreamingChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)
val telemetry = AppContext.getInstance().telemetry

Expand All @@ -194,6 +191,7 @@ class GeminiModelFactory : ChatModelFactory<GeminiSettings> {
thinkingConfig(
GeminiThinkingConfig.builder()
.thinkingLevel(geminiLevel)
.includeThoughts(true)
.build(),
)
sendThinking(true)
Expand All @@ -204,7 +202,7 @@ class GeminiModelFactory : ChatModelFactory<GeminiSettings> {
}

override fun createSecondaryModel(settings: GeminiSettings): ChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)
return GoogleAiGeminiChatModel.builder()
.httpClientBuilder(jdkHttpClientBuilder)
Expand All @@ -220,7 +218,7 @@ class GeminiModelFactory : ChatModelFactory<GeminiSettings> {
}

override fun createModel(settings: GeminiSettings): ChatModel {
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder())
val httpClientBuilder = ProxyUtil.configureProxy(HttpClient.newBuilder()).withLoggingIfDebug()
val jdkHttpClientBuilder = JdkHttpClient.builder().httpClientBuilder(httpClientBuilder)

return GoogleAiGeminiChatModel.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
*/
package io.askimo.core.telemetry

import dev.langchain4j.data.message.SystemMessage
import dev.langchain4j.model.chat.listener.ChatModelErrorContext
import dev.langchain4j.model.chat.listener.ChatModelListener
import dev.langchain4j.model.chat.listener.ChatModelRequestContext
import dev.langchain4j.model.chat.listener.ChatModelResponseContext
import io.askimo.core.analytics.Analytics
import io.askimo.core.analytics.AnalyticsEvent
import io.askimo.core.logging.logger
import io.askimo.core.memory.getTextContent
import io.askimo.core.util.MachineId
import java.net.URI
import java.net.http.HttpClient
Expand Down Expand Up @@ -97,57 +95,6 @@ class TelemetryChatModelListener(
}

attrs[ATTR_START_TIME] = System.currentTimeMillis()

val request = context.chatRequest()
if (log.isDebugEnabled) {
val messages = request.messages()
val systemMessages = messages.filterIsInstance<SystemMessage>()
val nonSystemMessages = messages.filter { it !is SystemMessage }
val lastMessages = nonSystemMessages.takeLast(3)
val params = request.parameters()

val debugMsg = buildString {
append("LLM request to $provider: ${messages.size} messages, ")
append("model=${request.modelName() ?: "default"}, ")
appendLine("clientId=$clientId, correlationId=$newCorrelationId")

if (systemMessages.isNotEmpty()) {
appendLine(" [System messages (${systemMessages.size})]")
systemMessages.forEach { msg ->
val preview = msg.getTextContent().take(300).replace('\n', ' ')
appendLine(" SYSTEM: $preview")
}
}

if (lastMessages.isNotEmpty()) {
appendLine(" [Last ${lastMessages.size} message(s)]")
lastMessages.forEach { msg ->
val role = msg.type().name
val preview = msg.getTextContent().take(300).replace('\n', ' ')
appendLine(" $role: $preview")
}
}

val paramParts = buildList {
params.modelName()?.let { add("modelName=$it") }
params.temperature()?.let { add("temperature=$it") }
params.topP()?.let { add("topP=$it") }
params.topK()?.let { add("topK=$it") }
params.frequencyPenalty()?.let { add("frequencyPenalty=$it") }
params.presencePenalty()?.let { add("presencePenalty=$it") }
params.maxOutputTokens()?.let { add("maxOutputTokens=$it") }
params.stopSequences()?.takeIf { it.isNotEmpty() }?.let { add("stopSequences=$it") }
params.responseFormat()?.let { add("responseFormat=$it") }
params.toolChoice()?.let { add("toolChoice=$it") }
params.toolSpecifications()?.takeIf { it.isNotEmpty() }?.let { tools -> add("tools(${tools.size})=${tools.map { it.name() }}") }
}
if (paramParts.isNotEmpty()) {
append(" [Parameters] ")
append(paramParts.joinToString(", "))
}
}
log.debug(debugMsg.trimEnd())
}
}

override fun onResponse(context: ChatModelResponseContext) {
Expand Down
Loading