feat(inference): capture reasoning_content on responses (WIP, provider side)

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).
This commit is contained in:
2026-07-06 01:26:01 +04:00
parent ff1a0ffdad
commit 28e9e698a1
4 changed files with 7 additions and 0 deletions
@@ -11,4 +11,8 @@ data class InferenceResponse(
val tokensUsed: TokenUsage,
val latencyMs: Long,
val toolCalls: List<ToolCallRequest> = 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,
)
@@ -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<ToolCallRequest> = emptyList(),
@SerialName("tool_call_id") val toolCallId: String? = null,
)
@@ -215,6 +215,7 @@ class LlamaCppInferenceProvider(
),
latencyMs = System.currentTimeMillis() - startTime,
toolCalls = toolCalls,
reasoning = message.reasoningContent?.takeIf { it.isNotBlank() },
)
}
@@ -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<ToolCallRequest> = emptyList(),
@SerialName("tool_call_id") val toolCallId: String? = null,
)