feat: add reasoning format processing to LLMNode for <think> tag handling (#23313)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
taewoong Kim
2025-09-05 19:15:35 +09:00
committed by GitHub
parent 05cd7e2d8a
commit edf4a1b652
30 changed files with 366 additions and 5 deletions

View File

@ -479,6 +479,10 @@ export const LLM_OUTPUT_STRUCT: Var[] = [
variable: 'text',
type: VarType.string,
},
{
variable: 'reasoning_content',
type: VarType.string,
},
{
variable: 'usage',
type: VarType.object,

View File

@ -0,0 +1,40 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Switch from '@/app/components/base/switch'
type ReasoningFormatConfigProps = {
value?: 'tagged' | 'separated'
onChange: (value: 'tagged' | 'separated') => void
readonly?: boolean
}
const ReasoningFormatConfig: FC<ReasoningFormatConfigProps> = ({
value = 'tagged',
onChange,
readonly = false,
}) => {
const { t } = useTranslation()
return (
<Field
title={t('workflow.nodes.llm.reasoningFormat.title')}
tooltip={t('workflow.nodes.llm.reasoningFormat.tooltip')}
operations={
// ON = separated, OFF = tagged
<Switch
defaultValue={value === 'separated'}
onChange={enabled => onChange(enabled ? 'separated' : 'tagged')}
size='md'
disabled={readonly}
key={value}
/>
}
>
<div />
</Field>
)
}
export default ReasoningFormatConfig

View File

@ -17,6 +17,7 @@ import type { NodePanelProps } from '@/app/components/workflow/types'
import Tooltip from '@/app/components/base/tooltip'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import StructureOutput from './components/structure-output'
import ReasoningFormatConfig from './components/reasoning-format-config'
import Switch from '@/app/components/base/switch'
import { RiAlertFill, RiQuestionLine } from '@remixicon/react'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
@ -61,6 +62,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleStructureOutputEnableChange,
handleStructureOutputChange,
filterJinja2InputVar,
handleReasoningFormatChange,
} = useConfig(id, data)
const model = inputs.model
@ -239,6 +241,14 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
config={inputs.vision?.configs}
onConfigChange={handleVisionResolutionChange}
/>
{/* Reasoning Format */}
<ReasoningFormatConfig
// Default to tagged for backward compatibility
value={inputs.reasoning_format || 'tagged'}
onChange={handleReasoningFormatChange}
readonly={readOnly}
/>
</div>
<Split />
<OutputVars

View File

@ -17,6 +17,7 @@ export type LLMNodeType = CommonNodeType & {
}
structured_output_enabled?: boolean
structured_output?: StructuredOutput
reasoning_format?: 'tagged' | 'separated'
}
export enum Type {

View File

@ -315,6 +315,14 @@ const useConfig = (id: string, payload: LLMNodeType) => {
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, [])
// reasoning format
const handleReasoningFormatChange = useCallback((reasoningFormat: 'tagged' | 'separated') => {
const newInputs = produce(inputs, (draft) => {
draft.reasoning_format = reasoningFormat
})
setInputs(newInputs)
}, [inputs, setInputs])
const {
availableVars,
availableNodesWithParent,
@ -355,6 +363,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
setStructuredOutputCollapsed,
handleStructureOutputEnableChange,
filterJinja2InputVar,
handleReasoningFormatChange,
}
}

View File

@ -470,6 +470,12 @@ const translation = {
instruction: 'Anleitung',
regenerate: 'Regenerieren',
},
reasoningFormat: {
tooltip: 'Inhalte aus Denk-Tags extrahieren und im Feld reasoning_content speichern.',
separated: 'Separate Denk tags',
title: 'Aktivieren Sie die Trennung von Argumentations-Tags',
tagged: 'Behalte die Denk-Tags',
},
},
knowledgeRetrieval: {
queryVariable: 'Abfragevariable',

View File

@ -449,6 +449,12 @@ const translation = {
variable: 'Variable',
},
sysQueryInUser: 'sys.query in user message is required',
reasoningFormat: {
title: 'Enable reasoning tag separation',
tagged: 'Keep think tags',
separated: 'Separate think tags',
tooltip: 'Extract content from think tags and store it in the reasoning_content field.',
},
jsonSchema: {
title: 'Structured Output Schema',
instruction: 'Instruction',

View File

@ -470,6 +470,12 @@ const translation = {
import: 'Importar desde JSON',
resetDefaults: 'Restablecer',
},
reasoningFormat: {
tagged: 'Mantén las etiquetas de pensamiento',
separated: 'Separar etiquetas de pensamiento',
title: 'Habilitar la separación de etiquetas de razonamiento',
tooltip: 'Extraer contenido de las etiquetas de pensamiento y almacenarlo en el campo reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Variable de consulta',

View File

@ -470,6 +470,12 @@ const translation = {
fieldNamePlaceholder: 'نام میدان',
generationTip: 'شما می‌توانید از زبان طبیعی برای ایجاد سریع یک طرح‌واره JSON استفاده کنید.',
},
reasoningFormat: {
separated: 'تگ‌های تفکر جداگانه',
title: 'فعال‌سازی جداسازی برچسب‌های استدلال',
tagged: 'به فکر برچسب‌ها باشید',
tooltip: 'محتوا را از تگ‌های تفکر استخراج کرده و در فیلد reasoning_content ذخیره کنید.',
},
},
knowledgeRetrieval: {
queryVariable: 'متغیر جستجو',

View File

@ -470,6 +470,12 @@ const translation = {
generateJsonSchema: 'Générer un schéma JSON',
resultTip: 'Voici le résultat généré. Si vous n\'êtes pas satisfait, vous pouvez revenir en arrière et modifier votre demande.',
},
reasoningFormat: {
title: 'Activer la séparation des balises de raisonnement',
tagged: 'Gardez les étiquettes de pensée',
separated: 'Séparer les balises de réflexion',
tooltip: 'Extraire le contenu des balises think et le stocker dans le champ reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Variable de requête',

View File

@ -483,6 +483,12 @@ const translation = {
required: 'आवश्यक',
addChildField: 'बच्चे का क्षेत्र जोड़ें',
},
reasoningFormat: {
title: 'कारण संबंध टैग विभाजन सक्षम करें',
separated: 'अलग सोच टैग',
tagged: 'टैग्स के बारे में सोचते रहें',
tooltip: 'थिंक टैग से सामग्री निकाले और इसे reasoning_content क्षेत्र में संग्रहित करें।',
},
},
knowledgeRetrieval: {
queryVariable: 'प्रश्न वेरिएबल',

View File

@ -487,6 +487,12 @@ const translation = {
generating: 'Generazione dello schema JSON...',
generatedResult: 'Risultato generato',
},
reasoningFormat: {
title: 'Abilita la separazione dei tag di ragionamento',
tagged: 'Continua a pensare ai tag',
separated: 'Tag di pensiero separati',
tooltip: 'Estrai il contenuto dai tag think e conservalo nel campo reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Variabile Query',

View File

@ -477,6 +477,12 @@ const translation = {
saveSchema: '編集中のフィールドを確定してから保存してください。',
},
},
reasoningFormat: {
tagged: 'タグを考え続けてください',
separated: '思考タグを分ける',
title: '推論タグの分離を有効にする',
tooltip: 'thinkタグから内容を抽出し、それをreasoning_contentフィールドに保存します。',
},
},
knowledgeRetrieval: {
queryVariable: '検索変数',

View File

@ -497,6 +497,12 @@ const translation = {
doc: '구조화된 출력에 대해 더 알아보세요.',
import: 'JSON 에서 가져오기',
},
reasoningFormat: {
title: '추론 태그 분리 활성화',
separated: '추론 태그 분리',
tooltip: '추론 태그에서 내용을 추출하고 이를 reasoning_content 필드에 저장합니다',
tagged: '추론 태그 유지',
},
},
knowledgeRetrieval: {
queryVariable: '쿼리 변수',

View File

@ -470,6 +470,12 @@ const translation = {
back: 'Tył',
addField: 'Dodaj pole',
},
reasoningFormat: {
tooltip: 'Wyodrębnij treść z tagów think i przechowaj ją w polu reasoning_content.',
separated: 'Oddziel tagi myślenia',
tagged: 'Zachowaj myśl tagi',
title: 'Włącz separację tagów uzasadnienia',
},
},
knowledgeRetrieval: {
queryVariable: 'Zmienna zapytania',

View File

@ -470,6 +470,12 @@ const translation = {
apply: 'Aplicar',
required: 'obrigatório',
},
reasoningFormat: {
tagged: 'Mantenha as tags de pensamento',
title: 'Ativar separação de tags de raciocínio',
separated: 'Separe as tags de pensamento',
tooltip: 'Extraia o conteúdo das tags de pensamento e armazene-o no campo reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Variável de consulta',

View File

@ -470,6 +470,12 @@ const translation = {
back: 'Înapoi',
promptPlaceholder: 'Descrie schema ta JSON...',
},
reasoningFormat: {
tagged: 'Ține minte etichetele',
separated: 'Etichete de gândire separate',
title: 'Activează separarea etichetelor de raționare',
tooltip: 'Extrage conținutul din etichetele think și stochează-l în câmpul reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Variabilă de interogare',

View File

@ -470,6 +470,12 @@ const translation = {
generating: 'Генерация схемы JSON...',
promptTooltip: 'Преобразуйте текстовое описание в стандартизированную структуру JSON Schema.',
},
reasoningFormat: {
tagged: 'Продолжайте думать о тегах',
title: 'Включите разделение тегов на основе логики',
tooltip: 'Извлечь содержимое из тегов think и сохранить его в поле reasoning_content.',
separated: 'Отдельные теги для мышления',
},
},
knowledgeRetrieval: {
queryVariable: 'Переменная запроса',

View File

@ -477,6 +477,12 @@ const translation = {
context: 'kontekst',
addMessage: 'Dodaj sporočilo',
vision: 'vizija',
reasoningFormat: {
tagged: 'Ohranite oznake za razmišljanje',
title: 'Omogoči ločevanje oznak za razsojanje',
tooltip: 'Izvleći vsebino iz miselnih oznak in jo shraniti v polje reasoning_content.',
separated: 'Ločite oznake za razmišljanje',
},
},
knowledgeRetrieval: {
outputVars: {

View File

@ -470,6 +470,12 @@ const translation = {
stringValidations: 'การตรวจสอบสตริง',
required: 'จำเป็นต้องใช้',
},
reasoningFormat: {
tagged: 'รักษาความคิดเกี่ยวกับแท็ก',
separated: 'แยกแท็กความคิดเห็น',
tooltip: 'ดึงเนื้อหาจากแท็กคิดและเก็บไว้ในฟิลด์ reasoning_content.',
title: 'เปิดใช้งานการแยกแท็กการเหตุผล',
},
},
knowledgeRetrieval: {
queryVariable: 'ตัวแปรแบบสอบถาม',

View File

@ -470,6 +470,12 @@ const translation = {
addChildField: 'Çocuk Alanı Ekle',
resultTip: 'İşte oluşturulan sonuç. Eğer memnun değilseniz, geri dönüp isteminizi değiştirebilirsiniz.',
},
reasoningFormat: {
separated: 'Ayrı düşünce etiketleri',
title: 'Akıl yürütme etiket ayrımını etkinleştir',
tagged: 'Etiketleri düşünmeye devam et',
tooltip: 'Düşünce etiketlerinden içeriği çıkarın ve bunu reasoning_content alanında saklayın.',
},
},
knowledgeRetrieval: {
queryVariable: 'Sorgu Değişkeni',

View File

@ -470,6 +470,12 @@ const translation = {
title: 'Структурована схема виходу',
doc: 'Дізнайтеся більше про структурований вихід',
},
reasoningFormat: {
separated: 'Окремі теги для думок',
tagged: 'Продовжуйте думати про мітки',
title: 'Увімкніть розділення тегів для міркування',
tooltip: 'Витягніть вміст з тегів think і зберігайте його в полі reasoning_content.',
},
},
knowledgeRetrieval: {
queryVariable: 'Змінна запиту',

View File

@ -470,6 +470,12 @@ const translation = {
addChildField: 'Thêm trường trẻ em',
title: 'Sơ đồ đầu ra có cấu trúc',
},
reasoningFormat: {
tagged: 'Giữ lại thẻ suy nghĩ',
tooltip: 'Trích xuất nội dung từ các thẻ think và lưu nó vào trường reasoning_content.',
separated: 'Tách biệt các thẻ suy nghĩ',
title: 'Bật chế độ phân tách nhãn lý luận',
},
},
knowledgeRetrieval: {
queryVariable: 'Biến truy vấn',

View File

@ -477,6 +477,12 @@ const translation = {
saveSchema: '请先完成当前字段的编辑',
},
},
reasoningFormat: {
tooltip: '从think标签中提取内容并将其存储在reasoning_content字段中。',
title: '启用推理标签分离',
tagged: '保持思考标签',
separated: '分开思考标签',
},
},
knowledgeRetrieval: {
queryVariable: '查询变量',

View File

@ -470,6 +470,12 @@ const translation = {
required: '必需的',
resultTip: '這是生成的結果。如果您不滿意,可以回去修改您的提示。',
},
reasoningFormat: {
title: '啟用推理標籤分離',
tooltip: '從 think 標籤中提取內容並將其存儲在 reasoning_content 欄位中。',
tagged: '保持思考標籤',
separated: '分開思考標籤',
},
},
knowledgeRetrieval: {
queryVariable: '查詢變量',