@@ -5,19 +5,24 @@ import ai.koog.prompt.dsl.Prompt
55import ai.koog.prompt.executor.clients.ConnectionTimeoutConfig
66import ai.koog.prompt.executor.clients.LLMClient
77import ai.koog.prompt.executor.clients.openai.base.AbstractOpenAILLMClient
8- import ai.koog.prompt.executor.clients.openai.base.OpenAIBasedSettings
8+ import ai.koog.prompt.executor.clients.openai.base.OpenAIBaseSettings
9+ import ai.koog.prompt.executor.clients.openai.base.OpenAICompatibleToolDescriptorSchemaGenerator
910import ai.koog.prompt.executor.clients.openai.base.models.OpenAIMessage
1011import ai.koog.prompt.executor.clients.openai.base.models.OpenAITool
1112import ai.koog.prompt.executor.clients.openai.base.models.OpenAIToolChoice
12- import ai.koog.prompt.executor.model.LLMChoice
1313import ai.koog.prompt.llm.LLMProvider
1414import ai.koog.prompt.llm.LLModel
15+ import ai.koog.prompt.message.Message
1516import ai.koog.prompt.params.LLMParams
16- import ai.koog.prompt.streaming.StreamFrameFlowBuilder
17+ import ai.koog.prompt.streaming.StreamFrame
1718import io.github.oshai.kotlinlogging.KotlinLogging
1819import io.ktor.client.*
19- import kotlinx.datetime.Clock
20+ import kotlinx.coroutines.flow.Flow
21+ import kotlinx.coroutines.flow.collect
22+ import kotlinx.coroutines.flow.flow
2023import kotlinx.serialization.Serializable
24+ import kotlin.time.Clock
25+ import kotlin.time.ExperimentalTime
2126
2227/* *
2328 * Configuration settings for custom OpenAI-compatible APIs (like GLM, custom endpoints, etc.)
@@ -30,7 +35,7 @@ class CustomOpenAIClientSettings(
3035 baseUrl : String ,
3136 chatCompletionsPath : String = " chat/completions" ,
3237 timeoutConfig : ConnectionTimeoutConfig = ConnectionTimeoutConfig ()
33- ) : OpenAIBasedSettings (baseUrl, chatCompletionsPath, timeoutConfig)
38+ ) : OpenAIBaseSettings (baseUrl, chatCompletionsPath, timeoutConfig)
3439
3540/* *
3641 * Request model for custom OpenAI-compatible chat completion
@@ -122,6 +127,7 @@ data class CustomOpenAIChatCompletionStreamResponse(
122127 * @param baseClient Optional custom HTTP client
123128 * @param clock Clock instance for tracking timestamps
124129 */
130+ @OptIn(ExperimentalTime ::class )
125131class CustomOpenAILLMClient (
126132 apiKey : String ,
127133 baseUrl : String ,
@@ -143,18 +149,15 @@ class CustomOpenAILLMClient(
143149 }
144150 }
145151 },
152+ " custom-openai" ,
146153 clock,
147- staticLogger
154+ staticLogger,
155+ OpenAICompatibleToolDescriptorSchemaGenerator ()
148156) {
149157
150158 private companion object {
151159 private val staticLogger = KotlinLogging .logger { }
152160
153- init {
154- // Register custom OpenAI JSON schema generators for structured output
155- // Use OpenAI provider since custom providers are OpenAI-compatible
156- registerOpenAIJsonSchemaGenerators(LLMProvider .OpenAI )
157- }
158161 }
159162
160163 override fun llmProvider (): LLMProvider = LLMProvider .OpenAI // OpenAI-compatible provider
@@ -194,7 +197,7 @@ class CustomOpenAILLMClient(
194197 return json.encodeToString(request)
195198 }
196199
197- override fun processProviderChatResponse (response : CustomOpenAIChatCompletionResponse ): List <LLMChoice > {
200+ override fun processProviderChatResponse (response : CustomOpenAIChatCompletionResponse ): List <List < Message . Response > > {
198201 require(response.choices.isNotEmpty()) { " Empty choices in response" }
199202 return response.choices.map {
200203 it.message.toMessageResponses(
@@ -210,13 +213,23 @@ class CustomOpenAILLMClient(
210213 override fun decodeResponse (data : String ): CustomOpenAIChatCompletionResponse =
211214 json.decodeFromString(data)
212215
213- override suspend fun StreamFrameFlowBuilder.processStreamingChunk (chunk : CustomOpenAIChatCompletionStreamResponse ) {
214- chunk.choices.firstOrNull()?.let { choice ->
215- choice.delta.content?.let { emitAppend(it) }
216- choice.delta.toolCalls?.forEach { toolCall ->
217- upsertToolCall(0 , toolCall.id, toolCall.function.name, toolCall.function.arguments)
216+ override fun processStreamingResponse (
217+ response : Flow <CustomOpenAIChatCompletionStreamResponse >
218+ ): Flow <StreamFrame > = flow {
219+ response.collect { chunk ->
220+ chunk.choices.firstOrNull()?.let { choice ->
221+ choice.delta.content?.let { emit(StreamFrame .TextDelta (it)) }
222+ choice.delta.toolCalls?.forEach { toolCall ->
223+ emit(
224+ StreamFrame .ToolCallDelta (
225+ id = toolCall.id,
226+ name = toolCall.function.name,
227+ content = toolCall.function.arguments
228+ )
229+ )
230+ }
231+ choice.finishReason?.let { emit(StreamFrame .End (it, createMetaInfo(chunk.usage))) }
218232 }
219- choice.finishReason?.let { emitEnd(it, createMetaInfo(chunk.usage)) }
220233 }
221234 }
222235
@@ -225,4 +238,3 @@ class CustomOpenAILLMClient(
225238 throw UnsupportedOperationException (" Moderation is not supported by custom OpenAI-compatible APIs." )
226239 }
227240}
228-
0 commit comments