jan/web-app/src/utils/reasoning.ts
Akarshan Biswas 906b87022d
chore: re enable reasoning_content in backend (#6228)
* chore: re enable reasoning_content in backend

* chore: handle reasoning_content

* chore: refactor get reasoning content

* chore: update PR review

---------

Co-authored-by: Faisal Amir <urmauur@gmail.com>
2025-08-20 13:06:21 +05:30

80 lines
2.2 KiB
TypeScript

import { CompletionResponseChunk } from 'token.js'
import {
chatCompletionChunk,
ChatCompletionMessage,
chatCompletionRequestMessage,
} from '@janhq/core'
// Helper function to get reasoning content from an object
function getReasoning(obj: { reasoning_content?: string | null; reasoning?: string | null } | null | undefined): string | null {
return obj?.reasoning_content ?? obj?.reasoning ?? null
}
// Extract reasoning from a message (for completed responses)
export function extractReasoningFromMessage(
message: chatCompletionRequestMessage | ChatCompletionMessage
): string | null {
if (!message) return null
const extendedMessage = message as chatCompletionRequestMessage
return getReasoning(extendedMessage)
}
// Extract reasoning from a chunk (for streaming responses)
function extractReasoningFromChunk(
chunk: CompletionResponseChunk | chatCompletionChunk
): string | null {
if (!chunk.choices?.[0]?.delta) return null
const delta = chunk.choices[0].delta as chatCompletionRequestMessage
const reasoning = getReasoning(delta)
// Return null for falsy values, non-strings, or whitespace-only strings
if (!reasoning || typeof reasoning !== 'string' || !reasoning.trim())
return null
return reasoning
}
// Tracks reasoning state and appends reasoning tokens with proper think tags
export class ReasoningProcessor {
private isReasoningActive = false
processReasoningChunk(
chunk: CompletionResponseChunk | chatCompletionChunk
): string {
const reasoning = extractReasoningFromChunk(chunk)
const chunkContent = chunk.choices?.[0]?.delta?.content || ''
// Handle reasoning tokens
if (reasoning) {
if (!this.isReasoningActive) {
this.isReasoningActive = true
return '<think>' + reasoning
}
return reasoning
}
// Handle reasoning end when content starts
if (this.isReasoningActive && chunkContent) {
this.isReasoningActive = false
return '</think>'
}
// No reasoning to process
return ''
}
finalize(): string {
if (this.isReasoningActive) {
this.isReasoningActive = false
return '</think>'
}
return ''
}
isReasoningInProgress(): boolean {
return this.isReasoningActive
}
}