From 28e9e698a1992ea9335acd1a4217712254e58686 Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 6 Jul 2026 01:26:01 +0400 Subject: [PATCH] feat(inference): capture reasoning_content on responses (WIP, provider side) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a nullable `reasoning` field to InferenceResponse and maps the llama.cpp / OpenAI-compat `reasoning_content` field into it, so local models that emit their chain-of-thought have it captured. Provider/model side only — the emit-site event field (reasoningArtifactId) + CAS storage in the orchestrator are still pending (tracked in Vikunja Correx #4). --- .../kotlin/com/correx/core/inference/InferenceResponse.kt | 4 ++++ .../infrastructure/inference/llama/cpp/LlamaCppApiModels.kt | 1 + .../inference/llama/cpp/LlamaCppInferenceProvider.kt | 1 + .../correx/infrastructure/inference/openai/OpenAiApiModels.kt | 1 + 4 files changed, 7 insertions(+) diff --git a/core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt b/core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt index db731ba6..bbcbcdf9 100644 --- a/core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt +++ b/core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt @@ -11,4 +11,8 @@ data class InferenceResponse( val tokensUsed: TokenUsage, val latencyMs: Long, val toolCalls: List = emptyList(), + // The model's chain-of-thought, when the provider exposes it separately (llama.cpp / OpenAI-compat + // `reasoning_content`). Captured for observability/audit — many local models emit their whole plan + // here and leave `text` empty, so dropping it hid why a stage behaved as it did (2026-07-05). + val reasoning: String? = null, ) diff --git a/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppApiModels.kt b/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppApiModels.kt index 12821e0c..e71a579e 100644 --- a/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppApiModels.kt +++ b/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppApiModels.kt @@ -24,6 +24,7 @@ data class ChatCompletionRequest( data class LlamaCppChatMessage( val role: String, val content: String?, + @SerialName("reasoning_content") val reasoningContent: String? = null, @SerialName("tool_calls") val toolCalls: List = emptyList(), @SerialName("tool_call_id") val toolCallId: String? = null, ) diff --git a/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppInferenceProvider.kt b/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppInferenceProvider.kt index 6ea2441a..65bbb22c 100644 --- a/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppInferenceProvider.kt +++ b/infrastructure/inference/llama_cpp/src/main/kotlin/com/correx/infrastructure/inference/llama/cpp/LlamaCppInferenceProvider.kt @@ -215,6 +215,7 @@ class LlamaCppInferenceProvider( ), latencyMs = System.currentTimeMillis() - startTime, toolCalls = toolCalls, + reasoning = message.reasoningContent?.takeIf { it.isNotBlank() }, ) } diff --git a/infrastructure/inference/openai_compat/src/main/kotlin/com/correx/infrastructure/inference/openai/OpenAiApiModels.kt b/infrastructure/inference/openai_compat/src/main/kotlin/com/correx/infrastructure/inference/openai/OpenAiApiModels.kt index b89e83e4..fb6cf427 100644 --- a/infrastructure/inference/openai_compat/src/main/kotlin/com/correx/infrastructure/inference/openai/OpenAiApiModels.kt +++ b/infrastructure/inference/openai_compat/src/main/kotlin/com/correx/infrastructure/inference/openai/OpenAiApiModels.kt @@ -28,6 +28,7 @@ data class OpenAiChatCompletionRequest( data class OpenAiChatMessage( val role: String, val content: String? = null, + @SerialName("reasoning_content") val reasoningContent: String? = null, @SerialName("tool_calls") val toolCalls: List = emptyList(), @SerialName("tool_call_id") val toolCallId: String? = null, )