This commit is contained in:
Typer_Body
2026-05-22 02:07:48 +08:00
parent f99d3022e8
commit 253cc6cbea
42 changed files with 727 additions and 237 deletions

View File

@@ -5,18 +5,61 @@ Node metadata is loaded from: ../../templates/metadata/nodes/call_pipeline.yaml
from __future__ import annotations
from typing import Any
from typing import Any, Optional
import pydantic
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_event_logger
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
import langbot_plugin.api.entities.builtin.platform.events as platform_events
import langbot_plugin.api.entities.builtin.platform.message as platform_message
import langbot_plugin.api.entities.builtin.provider.session as provider_session
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
class _NoOpEventLogger(abstract_event_logger.AbstractEventLogger):
"""No-op event logger for workflow pipeline adapter."""
async def info(
self,
text: str,
images: Optional[list[platform_message.Image]] = None,
message_session_id: Optional[str] = None,
no_throw: bool = True,
):
pass
async def debug(
self,
text: str,
images: Optional[list[platform_message.Image]] = None,
message_session_id: Optional[str] = None,
no_throw: bool = True,
):
pass
async def warning(
self,
text: str,
images: Optional[list[platform_message.Image]] = None,
message_session_id: Optional[str] = None,
no_throw: bool = True,
):
pass
async def error(
self,
text: str,
images: Optional[list[platform_message.Image]] = None,
message_session_id: Optional[str] = None,
no_throw: bool = True,
):
pass
@workflow_node('call_pipeline')
class CallPipelineNode(WorkflowNode):
"""Call pipeline node - invoke an existing pipeline"""
@@ -139,10 +182,16 @@ class CallPipelineNode(WorkflowNode):
)
class _WorkflowPipelineCaptureAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
"""Adapter to capture pipeline responses for workflow execution."""
class Config:
arbitrary_types_allowed = True
responses: list[dict[str, Any]] = []
context: ExecutionContext = pydantic.Field(exclude=True)
def __init__(self, context: ExecutionContext):
super().__init__(config={}, logger=None)
super().__init__(config={}, logger=_NoOpEventLogger())
self.context = context
self.responses = []

View File

@@ -9,7 +9,7 @@ import json
import re
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('code_executor')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
from ..safe_eval import safe_eval_with_vars

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('coze_bot')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('cron_trigger')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
from ..safe_eval import safe_eval_with_vars

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('database_query')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('dify_knowledge_query')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('dify_workflow')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('end')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('event_trigger')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('http_request')

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('iterator')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('knowledge_retrieval')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('langflow_flow')

View File

@@ -10,7 +10,7 @@ from typing import Any, AsyncGenerator
import langbot_plugin.api.entities.builtin.provider.message as provider_message
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
logger = logging.getLogger(__name__)
@@ -181,7 +181,6 @@ class LLMCallNode(WorkflowNode):
# Get error handling config
exception_handling = self.get_config('exception_handling', 'show-error')
failure_hint = self.get_config('failure_hint', 'Request failed.')
remove_think = self.get_config('remove_think', False)
track_function_calls = self.get_config('track_function_calls', False)
# Get output format and json_schema config
@@ -192,11 +191,10 @@ class LLMCallNode(WorkflowNode):
enable_tools = self.get_config('enable_tools', False)
tools_config = self.get_config('tools', [])
# Resolve prompts - handle null user_prompt_template
# Resolve prompts
system_prompt = self._resolve_template(self.get_config('system_prompt') or '', inputs, context)
user_prompt_template = self.get_config('user_prompt_template')
if user_prompt_template is None:
# Default to input if not set
user_prompt_template = '{{input}}'
user_prompt = self._resolve_template(user_prompt_template, inputs, context)
@@ -229,14 +227,11 @@ class LLMCallNode(WorkflowNode):
if tool_names:
all_tools = await self.ap.tool_mgr.get_tools()
funcs = [t for t in all_tools if t.name in tool_names]
if funcs:
logger.info(f'LLM call node {self.node_id}: using tools: {[t.name for t in funcs]}')
except Exception as e:
logger.warning(f'LLM call node {self.node_id}: failed to load tools - {e}')
logger.warning(f'[LLM:{self.node_id}] Failed to load tools: {e}')
funcs = None
# Invoke LLM with error handling
logger.info(f'LLM call node {self.node_id}: invoking model {model_uuid}')
# Invoke LLM
try:
result_message = await runtime_model.provider.invoke_llm(
query=None,
@@ -246,7 +241,7 @@ class LLMCallNode(WorkflowNode):
extra_args=extra_args,
)
except Exception as e:
logger.warning(f'LLM call node {self.node_id}: request failed - {e}')
logger.warning(f'[LLM:{self.node_id}] LLM call failed: {e}')
# Handle based on exception handling strategy
if exception_handling == 'show-error':
@@ -284,21 +279,20 @@ class LLMCallNode(WorkflowNode):
elif isinstance(elem, str):
response_text += elem
# Remove CoT (Chain of Thought) content if configured
if remove_think:
response_text = self._remove_think_content(response_text)
# Remove CoT content (always remove to avoid leaking internal reasoning)
response_text = self._remove_think_content(response_text)
# Apply content safety filter
response_text, is_blocked, filter_notice = self._apply_content_filter(response_text)
if is_blocked:
logger.warning(f'LLM call node {self.node_id}: response blocked by content filter - {filter_notice}')
logger.warning(f'[LLM:{self.node_id}] Response blocked by content filter: {filter_notice}')
return {
'response': filter_notice,
'usage': usage,
'blocked_by_filter': True,
}
# Extract usage info if available
# Extract usage info
usage = {
'prompt_tokens': 0,
'completion_tokens': 0,
@@ -319,6 +313,7 @@ class LLMCallNode(WorkflowNode):
'total_tokens': getattr(u, 'total_tokens', 0) or 0,
}
# Build result
result: dict[str, Any] = {
'response': response_text,
'usage': usage,
@@ -329,7 +324,7 @@ class LLMCallNode(WorkflowNode):
try:
result['parsed'] = json.loads(response_text)
except json.JSONDecodeError as e:
logger.warning(f'LLM call node {self.node_id}: failed to parse JSON response - {e}')
logger.warning(f'[LLM:{self.node_id}] Failed to parse JSON: {e}')
result['parsed'] = None
result['parse_error'] = str(e)
@@ -354,7 +349,6 @@ class LLMCallNode(WorkflowNode):
if not self.ap:
raise RuntimeError('Application instance not available - cannot call LLM')
remove_think = self.get_config('remove_think', False)
exception_handling = self.get_config('exception_handling', 'show-error')
failure_hint = self.get_config('failure_hint', 'Request failed.')
@@ -383,7 +377,7 @@ class LLMCallNode(WorkflowNode):
if max_tokens and int(max_tokens) > 0:
extra_args['max_tokens'] = int(max_tokens)
logger.info(f'LLM call node {self.node_id}: streaming model {model_uuid}')
logger.info(f'[LLM:{self.node_id}] Streaming model {model_uuid}')
try:
# Try streaming first
@@ -396,6 +390,7 @@ class LLMCallNode(WorkflowNode):
)
full_response = ''
in_think_block = False
async for chunk in stream:
chunk_text = ''
if hasattr(chunk, 'content'):
@@ -409,16 +404,25 @@ class LLMCallNode(WorkflowNode):
chunk_text += elem
if chunk_text:
if remove_think:
chunk_text = self._remove_think_content(chunk_text)
full_response += chunk_text
yield chunk_text
# Filter <think> blocks in streaming mode
if '<think>' in chunk_text or '<thought>' in chunk_text:
in_think_block = True
if in_think_block:
if '</think>' in chunk_text or '</thought>' in chunk_text:
in_think_block = False
chunk_text = chunk_text.split('</think>')[-1].split('</thought>')[-1]
else:
chunk_text = ''
if chunk_text:
full_response += chunk_text
yield chunk_text
# Store in context for downstream nodes
context.variables['_last_llm_response'] = full_response
except Exception as e:
logger.warning(f'LLM call node {self.node_id}: streaming failed, falling back - {e}')
logger.warning(f'[LLM:{self.node_id}] Streaming failed, falling back - {e}')
# Fallback to non-streaming
try:
result_message = await runtime_model.provider.invoke_llm(
@@ -429,12 +433,12 @@ class LLMCallNode(WorkflowNode):
extra_args=extra_args,
)
response_text = self._extract_response_text(result_message)
if remove_think:
response_text = self._remove_think_content(response_text)
# Always remove <think> content in fallback
response_text = self._remove_think_content(response_text)
yield response_text
context.variables['_last_llm_response'] = response_text
except Exception as e2:
logger.error(f'LLM call node {self.node_id}: fallback also failed - {e2}')
logger.error(f'[LLM:{self.node_id}] Fallback also failed - {e2}')
if exception_handling == 'show-hint':
yield failure_hint
elif exception_handling != 'hide':

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('loop')

View File

@@ -11,7 +11,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('mcp_tool')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
class MemoryHelper:

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('merge')

View File

@@ -9,7 +9,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('message_trigger')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('n8n_workflow')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('opening_statement')

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('parallel')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('parameter_extractor')

View File

@@ -7,7 +7,7 @@
# from typing import Any
# from ..entities import ExecutionContext
# from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
# from ..node import WorkflowNode, workflow_node
# @workflow_node('plugin_call')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('question_classifier')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('redis_operation')

View File

@@ -8,7 +8,7 @@ from __future__ import annotations
import logging
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
logger = logging.getLogger(__name__)
@@ -20,13 +20,14 @@ class ReplyMessageNode(WorkflowNode):
category = 'action'
async def execute(self, inputs: dict[str, Any], context: ExecutionContext) -> dict[str, Any]:
message = inputs.get('message')
if message in (None, ''):
message = inputs.get('input')
# Priority: content/response (from upstream nodes) > message (original input) > context
message = inputs.get('content')
if message in (None, ''):
message = inputs.get('response')
if message in (None, ''):
message = inputs.get('content')
message = inputs.get('input')
if message in (None, ''):
message = inputs.get('message')
if message in (None, '') and context.message_context:
message = context.message_context.message_content
if message is None:

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('send_message')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('set_variable')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('store_data')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('switch')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('variable_aggregator')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('wait')

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any
from ..entities import ExecutionContext
from langbot_plugin.api.entities.builtin.workflow import ExecutionContext
from ..node import WorkflowNode, workflow_node
@workflow_node('webhook_trigger')