fix(gateway): keep reasoning trace split compatible

This commit is contained in:
Rene Fichtmueller 2026-07-17 17:22:40 +02:00
parent 60529d7752
commit 496975ed94

View File

@ -1,18 +1,66 @@
export interface ReasoningTraceSplit { export interface ReasoningTraceSplit {
visible: string; visible: string;
trace: string | null; finalAnswer: string;
trace: {
marker: 'thinking_tag' | 'think_tag' | 'markdown_header' | 'json_field';
content: string;
estimatedTokens: number;
} | null;
} }
export function splitReasoningTrace(output: string): ReasoningTraceSplit { function estimateTokens(text: string): number {
const match = output.match(/<think>([\s\S]*?)<\/think>/i); return Math.max(1, Math.ceil(text.length / 4));
if (!match) return { visible: output, trace: null }; }
function splitWithTrace(
output: string,
marker: NonNullable<ReasoningTraceSplit['trace']>['marker'],
traceContent: string,
finalAnswer: string
): ReasoningTraceSplit {
const visible = finalAnswer.trim();
return { return {
visible: output.replace(match[0], '').trim(), visible,
trace: match[1]?.trim() ?? null, finalAnswer: visible,
trace: {
marker,
content: traceContent.trim(),
estimatedTokens: estimateTokens(traceContent),
},
}; };
} }
export async function storeReasoningTrace(_callId: string, _trace: string | null): Promise<void> { export function splitReasoningTrace(output: string): ReasoningTraceSplit {
const trimmed = output.trim();
const thinkingMatch = trimmed.match(/<thinking>([\s\S]*?)<\/thinking>/i);
if (thinkingMatch) {
return splitWithTrace(trimmed, 'thinking_tag', thinkingMatch[1] ?? '', trimmed.replace(thinkingMatch[0], ''));
}
const thinkMatch = trimmed.match(/<think>([\s\S]*?)<\/think>/i);
if (thinkMatch) {
return splitWithTrace(trimmed, 'think_tag', thinkMatch[1] ?? '', trimmed.replace(thinkMatch[0], ''));
}
const markdownMatch = trimmed.match(/\*\*Reasoning:\*\*([\s\S]*?)\*\*Answer:\*\*([\s\S]*)/i);
if (markdownMatch) {
return splitWithTrace(trimmed, 'markdown_header', markdownMatch[1] ?? '', markdownMatch[2] ?? '');
}
try {
const parsed = JSON.parse(trimmed) as { reasoning?: unknown; answer?: unknown };
if (typeof parsed.reasoning === 'string' && typeof parsed.answer === 'string') {
return splitWithTrace(trimmed, 'json_field', parsed.reasoning, parsed.answer);
}
} catch {
// Plain model output, not a reasoning envelope.
}
return { visible: trimmed, finalAnswer: trimmed, trace: null };
}
export async function storeReasoningTrace(_callId: string, _trace: ReasoningTraceSplit['trace']): Promise<void> {
// Reasoning trace persistence is optional. Keep the public route usable when // Reasoning trace persistence is optional. Keep the public route usable when
// the storage table has not been deployed yet. // the storage table has not been deployed yet.
} }