Reasoning Tag Filtering#
Dify extracts or preserves <think>…</think> reasoning tags emitted by chain-of-thought models (e.g., DeepSeek-R1, QwQ) through a two-mode architecture that applies uniformly to LLM nodes and Agent nodes in workflows.
Modes#
| Mode | Behavior | Default |
|---|---|---|
tagged | Tags pass through unchanged; downstream nodes see raw model output | ✅ Yes |
separated | Tags are stripped from the answer stream; reasoning content surfaces as a dedicated reasoning_content field | No |
tagged is the default in both LLMNodeData and AgentNodeData to preserve backward compatibility with existing workflows.
How Detection and Separation Work#
LLM Node (split_reasoning)#
The actual tag-splitting logic lives in graphon.nodes.llm.reasoning.split_reasoning — an external library imported by Dify . It runs a case-insensitive regex (<think[^>]*>(.*?)</think>) over the completed text after streaming finishes :
separatedmode: All<think>blocks are removed from the answer; their inner content is returned asreasoning_contenton theLLMResultand propagated throughModelInvokeCompletedEvent.taggedmode: Text is returned unchanged;reasoning_contentis empty.
During streaming, both modes forward raw chunks — including <think> tags — to the frontend so the real-time "thinking" panel continues to work. The post-stream cleanup only affects values exposed to downstream nodes .
Agent Node (ThinkStreamFilter)#
Agent nodes reuse the ThinkStreamFilter from graphon via message_transformer.py. The filter is stateful across chunks, so it correctly reassembles tags that span multiple streamed segments (e.g., "<thi" + "nk>…</think>") . In separated mode:
- Text chunks are routed through
ThinkStreamFilter; clean text is emitted asStreamChunkEvent. - Detected reasoning is emitted as
StreamReasoningEventon a separate channel. - After stream completion,
text_filter.finalize()flushes any buffered partial tags .
Event and Pipeline Architecture#
Reasoning content travels through the backend on its own event channel, never mixing with the answer stream:
QueueReasoningChunkEvent— internal queue event for out-of-band reasoning deltas; carriesreasoning: str,from_node_id, andis_final.ReasoningChunkStreamResponse— SSE response wrapper that sends the reasoning chunk to clients; parallel totext_chunk, only emitted inseparatedmode .- Pipeline handlers — both
advanced_chatandworkflowgenerate_task_pipeline.pyimplement_handle_reasoning_chunk_eventto forward these events and persist terminalreasoning_contentper LLM node ID intask_state.metadata.reasoning[node_id].
Special Case: Structured Output Parsing#
The structured output parser handles a common artifact from reasoning models: DeepSeek-R1 and similar models sometimes wrap JSON in <think>\n\n</think>\n. When json_repair.loads() returns a list (the <think> block parsed as an element), the parser extracts the first dict, effectively skipping the reasoning prefix .
Frontend Configuration#
reasoning-format-config.tsx— a Switch toggle on the LLM node panel lets users switch betweentaggedandseparatedmodes .panel-output-section.tsx— declaresreasoning_contentas a named string output variable on the LLM node, available for wiring to downstream nodes regardless of mode.types.ts— type:reasoning_format?: 'tagged' | 'separated'onLLMNodeType.- Agent node UI mirrors LLM node:
reasoning_formatfield added toAgentNodeTypeand its panel .
Key Source Files#
| File | Role |
|---|---|
api/core/workflow/nodes/llm/node.py | Invokes split_reasoning, emits ModelInvokeCompletedEvent with reasoning_content |
api/core/workflow/nodes/agent/message_transformer.py | Stream-level ThinkStreamFilter for agent nodes |
api/core/app/entities/queue_entities.py | QueueReasoningChunkEvent definition |
api/core/app/entities/task_entities.py | ReasoningChunkStreamResponse definition |
api/core/app/apps/advanced_chat/generate_task_pipeline.py | Reasoning persistence and SSE forwarding |
web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx | UI toggle |
api/core/llm_generator/output_parser/structured_output.py | Deepseek-R1 JSON prefix handling |