@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast
from core . app . entities . app_invoke_entities import ModelConfigWithCredentialsEntity
from core . app . entities . app_invoke_entities import ModelConfigWithCredentialsEntity
from core . file import FileType , file_manager
from core . file import FileType , file_manager
from core . helper . code_executor import CodeExecutor , CodeLanguage
from core . helper . code_executor import CodeExecutor , CodeLanguage
from core . llm_generator . output_parser . errors import OutputParserError
from core . llm_generator . output_parser . structured_output import invoke_llm_with_structured_output
from core . llm_generator . output_parser . structured_output import invoke_llm_with_structured_output
from core . memory . token_buffer_memory import TokenBufferMemory
from core . memory . token_buffer_memory import TokenBufferMemory
from core . model_manager import ModelInstance , ModelManager
from core . model_manager import ModelInstance , ModelManager
@ -340,26 +341,32 @@ class LLMNode(BaseNode[LLMNodeData]):
usage = LLMUsage . empty_usage ( )
usage = LLMUsage . empty_usage ( )
finish_reason = None
finish_reason = None
full_text_buffer = io . StringIO ( )
full_text_buffer = io . StringIO ( )
for result in invoke_result :
# Consume the invoke result and handle generator exception
if isinstance ( result , LLMResultChunk ) :
try :
contents = result . delta . message . content
for result in invoke_result :
for text_part in self . _save_multimodal_output_and_convert_result_to_markdown ( contents ) :
if isinstance ( result , LLMResultChunk ) :
full_text_buffer . write ( text_part )
contents = result . delta . message . content
yield RunStreamChunkEvent ( chunk_content = text_part , from_variable_selector = [ self . node_id , " text " ] )
for text_part in self . _save_multimodal_output_and_convert_result_to_markdown ( contents ) :
full_text_buffer . write ( text_part )
# Update the whole metadata
yield RunStreamChunkEvent (
if not model and result . model :
chunk_content = text_part , from_variable_selector = [ self . node_id , " text " ]
model = result . model
)
if len ( prompt_messages ) == 0 :
# TODO(QuantumGhost): it seems that this update has no visable effect.
# Update the whole metadata
# What's the purpose of the line below?
if not model and result . model :
prompt_messages = list ( result . prompt_messages )
model = result . model
if usage . prompt_tokens == 0 and result . delta . usage :
if len ( prompt_messages ) == 0 :
usage = result . delta . usage
# TODO(QuantumGhost): it seems that this update has no visable effect.
if finish_reason is None and result . delta . finish_reason :
# What's the purpose of the line below?
finish_reason = result . delta . finish_reason
prompt_messages = list ( result . prompt_messages )
elif isinstance ( result , LLMStructuredOutput ) :
if usage . prompt_tokens == 0 and result . delta . usage :
yield result
usage = result . delta . usage
if finish_reason is None and result . delta . finish_reason :
finish_reason = result . delta . finish_reason
elif isinstance ( result , LLMStructuredOutput ) :
yield result
except OutputParserError as e :
raise LLMNodeError ( f " Failed to parse structured output: { e } " )
yield ModelInvokeCompletedEvent ( text = full_text_buffer . getvalue ( ) , usage = usage , finish_reason = finish_reason )
yield ModelInvokeCompletedEvent ( text = full_text_buffer . getvalue ( ) , usage = usage , finish_reason = finish_reason )