mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 23:48:04 +08:00
refactor: rename mention node to nested_node for generic sub-graph support
This commit is contained in:
@ -46,8 +46,8 @@ from models.workflow import Workflow
|
||||
from services.app_generate_service import AppGenerateService
|
||||
from services.errors.app import WorkflowHashNotEqualError
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
from services.workflow.entities import MentionGraphRequest, MentionParameterSchema
|
||||
from services.workflow.mention_graph_service import MentionGraphService
|
||||
from services.workflow.entities import NestedNodeGraphRequest, NestedNodeParameterSchema
|
||||
from services.workflow.nested_node_graph_service import NestedNodeGraphService
|
||||
from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -190,8 +190,8 @@ class DraftWorkflowTriggerRunAllPayload(BaseModel):
|
||||
node_ids: list[str]
|
||||
|
||||
|
||||
class MentionGraphPayload(BaseModel):
|
||||
"""Request payload for generating mention graph."""
|
||||
class NestedNodeGraphPayload(BaseModel):
|
||||
"""Request payload for generating nested node graph."""
|
||||
|
||||
parent_node_id: str = Field(description="ID of the parent node that uses the extracted value")
|
||||
parameter_key: str = Field(description="Key of the parameter being extracted")
|
||||
@ -216,7 +216,7 @@ reg(WorkflowListQuery)
|
||||
reg(WorkflowUpdatePayload)
|
||||
reg(DraftWorkflowTriggerRunPayload)
|
||||
reg(DraftWorkflowTriggerRunAllPayload)
|
||||
reg(MentionGraphPayload)
|
||||
reg(NestedNodeGraphPayload)
|
||||
|
||||
|
||||
# TODO(QuantumGhost): Refactor existing node run API to handle file parameter parsing
|
||||
@ -1180,20 +1180,20 @@ class DraftWorkflowTriggerRunAllApi(Resource):
|
||||
), 400
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/mention-graph")
|
||||
class MentionGraphApi(Resource):
|
||||
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/nested-node-graph")
|
||||
class NestedNodeGraphApi(Resource):
|
||||
"""
|
||||
API for generating Mention LLM node graph structures.
|
||||
API for generating Nested Node LLM graph structures.
|
||||
|
||||
This endpoint creates a complete graph structure containing an LLM node
|
||||
configured to extract values from list[PromptMessage] variables.
|
||||
"""
|
||||
|
||||
@console_ns.doc("generate_mention_graph")
|
||||
@console_ns.doc(description="Generate a Mention LLM node graph structure")
|
||||
@console_ns.doc("generate_nested_node_graph")
|
||||
@console_ns.doc(description="Generate a Nested Node LLM graph structure")
|
||||
@console_ns.doc(params={"app_id": "Application ID"})
|
||||
@console_ns.expect(console_ns.models[MentionGraphPayload.__name__])
|
||||
@console_ns.response(200, "Mention graph generated successfully")
|
||||
@console_ns.expect(console_ns.models[NestedNodeGraphPayload.__name__])
|
||||
@console_ns.response(200, "Nested node graph generated successfully")
|
||||
@console_ns.response(400, "Invalid request parameters")
|
||||
@console_ns.response(403, "Permission denied")
|
||||
@setup_required
|
||||
@ -1203,21 +1203,21 @@ class MentionGraphApi(Resource):
|
||||
@edit_permission_required
|
||||
def post(self, app_model: App):
|
||||
"""
|
||||
Generate a Mention LLM node graph structure.
|
||||
Generate a Nested Node LLM graph structure.
|
||||
|
||||
Returns a complete graph structure containing a single LLM node
|
||||
configured for extracting values from list[PromptMessage] context.
|
||||
"""
|
||||
|
||||
payload = MentionGraphPayload.model_validate(console_ns.payload or {})
|
||||
payload = NestedNodeGraphPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
parameter_schema = MentionParameterSchema(
|
||||
parameter_schema = NestedNodeParameterSchema(
|
||||
name=payload.parameter_schema.get("name", payload.parameter_key),
|
||||
type=payload.parameter_schema.get("type", "string"),
|
||||
description=payload.parameter_schema.get("description", ""),
|
||||
)
|
||||
|
||||
request = MentionGraphRequest(
|
||||
request = NestedNodeGraphRequest(
|
||||
parent_node_id=payload.parent_node_id,
|
||||
parameter_key=payload.parameter_key,
|
||||
context_source=payload.context_source,
|
||||
@ -1225,7 +1225,7 @@ class MentionGraphApi(Resource):
|
||||
)
|
||||
|
||||
with Session(db.engine) as session:
|
||||
service = MentionGraphService(session)
|
||||
response = service.generate_mention_graph(tenant_id=app_model.tenant_id, request=request)
|
||||
service = NestedNodeGraphService(session)
|
||||
response = service.generate_nested_node_graph(tenant_id=app_model.tenant_id, request=request)
|
||||
|
||||
return response.model_dump()
|
||||
|
||||
@ -70,8 +70,8 @@ class _NodeSnapshot:
|
||||
"""Empty string means the node is not executing inside an iteration."""
|
||||
loop_id: str = ""
|
||||
"""Empty string means the node is not executing inside a loop."""
|
||||
mention_parent_id: str = ""
|
||||
"""Empty string means the node is not an extractor node."""
|
||||
parent_node_id: str = ""
|
||||
"""Empty string means the node is not an nested node (extractor node)."""
|
||||
|
||||
|
||||
class WorkflowResponseConverter:
|
||||
@ -133,7 +133,7 @@ class WorkflowResponseConverter:
|
||||
start_at=event.start_at,
|
||||
iteration_id=event.in_iteration_id or "",
|
||||
loop_id=event.in_loop_id or "",
|
||||
mention_parent_id=event.in_mention_parent_id or "",
|
||||
parent_node_id=event.in_parent_node_id or "",
|
||||
)
|
||||
node_execution_id = NodeExecutionId(event.node_execution_id)
|
||||
self._node_snapshots[node_execution_id] = snapshot
|
||||
@ -290,7 +290,7 @@ class WorkflowResponseConverter:
|
||||
created_at=int(snapshot.start_at.timestamp()),
|
||||
iteration_id=event.in_iteration_id,
|
||||
loop_id=event.in_loop_id,
|
||||
mention_parent_id=event.in_mention_parent_id,
|
||||
parent_node_id=event.in_parent_node_id,
|
||||
agent_strategy=event.agent_strategy,
|
||||
),
|
||||
)
|
||||
@ -377,7 +377,7 @@ class WorkflowResponseConverter:
|
||||
files=self.fetch_files_from_node_outputs(event.outputs or {}),
|
||||
iteration_id=event.in_iteration_id,
|
||||
loop_id=event.in_loop_id,
|
||||
mention_parent_id=event.in_mention_parent_id,
|
||||
parent_node_id=event.in_parent_node_id,
|
||||
),
|
||||
)
|
||||
|
||||
@ -427,7 +427,7 @@ class WorkflowResponseConverter:
|
||||
files=self.fetch_files_from_node_outputs(event.outputs or {}),
|
||||
iteration_id=event.in_iteration_id,
|
||||
loop_id=event.in_loop_id,
|
||||
mention_parent_id=event.in_mention_parent_id,
|
||||
parent_node_id=event.in_parent_node_id,
|
||||
retry_index=event.retry_index,
|
||||
),
|
||||
)
|
||||
|
||||
@ -385,7 +385,7 @@ class WorkflowBasedAppRunner:
|
||||
start_at=event.start_at,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
inputs=inputs,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
@ -406,7 +406,7 @@ class WorkflowBasedAppRunner:
|
||||
start_at=event.start_at,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
agent_strategy=event.agent_strategy,
|
||||
provider_type=event.provider_type,
|
||||
provider_id=event.provider_id,
|
||||
@ -430,7 +430,7 @@ class WorkflowBasedAppRunner:
|
||||
execution_metadata=execution_metadata,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, NodeRunFailedEvent):
|
||||
@ -447,7 +447,7 @@ class WorkflowBasedAppRunner:
|
||||
execution_metadata=event.node_run_result.metadata,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, NodeRunExceptionEvent):
|
||||
@ -464,7 +464,7 @@ class WorkflowBasedAppRunner:
|
||||
execution_metadata=event.node_run_result.metadata,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, NodeRunStreamChunkEvent):
|
||||
@ -482,7 +482,7 @@ class WorkflowBasedAppRunner:
|
||||
chunk_type=QueueChunkType(event.chunk_type.value),
|
||||
tool_call=event.tool_call,
|
||||
tool_result=event.tool_result,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, NodeRunRetrieverResourceEvent):
|
||||
@ -491,7 +491,7 @@ class WorkflowBasedAppRunner:
|
||||
retriever_resources=event.retriever_resources,
|
||||
in_iteration_id=event.in_iteration_id,
|
||||
in_loop_id=event.in_loop_id,
|
||||
in_mention_parent_id=event.in_mention_parent_id,
|
||||
in_parent_node_id=event.in_parent_node_id,
|
||||
)
|
||||
)
|
||||
elif isinstance(event, NodeRunAgentLogEvent):
|
||||
|
||||
@ -201,7 +201,7 @@ class QueueTextChunkEvent(AppQueueEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
|
||||
# Extended fields for Agent/Tool streaming
|
||||
@ -252,7 +252,7 @@ class QueueRetrieverResourcesEvent(AppQueueEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
|
||||
|
||||
@ -331,7 +331,7 @@ class QueueNodeStartedEvent(AppQueueEvent):
|
||||
node_run_index: int = 1 # FIXME(-LAN-): may not used
|
||||
in_iteration_id: str | None = None
|
||||
in_loop_id: str | None = None
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
start_at: datetime
|
||||
agent_strategy: AgentNodeStrategyInit | None = None
|
||||
@ -355,7 +355,7 @@ class QueueNodeSucceededEvent(AppQueueEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
start_at: datetime
|
||||
|
||||
@ -412,7 +412,7 @@ class QueueNodeExceptionEvent(AppQueueEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
start_at: datetime
|
||||
|
||||
@ -438,7 +438,7 @@ class QueueNodeFailedEvent(AppQueueEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
in_parent_node_id: str | None = None
|
||||
"""parent node id if this is an extractor node event"""
|
||||
start_at: datetime
|
||||
|
||||
|
||||
@ -294,7 +294,7 @@ class NodeStartStreamResponse(StreamResponse):
|
||||
extras: dict[str, object] = Field(default_factory=dict)
|
||||
iteration_id: str | None = None
|
||||
loop_id: str | None = None
|
||||
mention_parent_id: str | None = None
|
||||
parent_node_id: str | None = None
|
||||
agent_strategy: AgentNodeStrategyInit | None = None
|
||||
|
||||
event: StreamEvent = StreamEvent.NODE_STARTED
|
||||
@ -318,7 +318,7 @@ class NodeStartStreamResponse(StreamResponse):
|
||||
"extras": {},
|
||||
"iteration_id": self.data.iteration_id,
|
||||
"loop_id": self.data.loop_id,
|
||||
"mention_parent_id": self.data.mention_parent_id,
|
||||
"parent_node_id": self.data.parent_node_id,
|
||||
},
|
||||
}
|
||||
|
||||
@ -354,7 +354,7 @@ class NodeFinishStreamResponse(StreamResponse):
|
||||
files: Sequence[Mapping[str, Any]] | None = []
|
||||
iteration_id: str | None = None
|
||||
loop_id: str | None = None
|
||||
mention_parent_id: str | None = None
|
||||
parent_node_id: str | None = None
|
||||
|
||||
event: StreamEvent = StreamEvent.NODE_FINISHED
|
||||
workflow_run_id: str
|
||||
@ -384,7 +384,7 @@ class NodeFinishStreamResponse(StreamResponse):
|
||||
"files": [],
|
||||
"iteration_id": self.data.iteration_id,
|
||||
"loop_id": self.data.loop_id,
|
||||
"mention_parent_id": self.data.mention_parent_id,
|
||||
"parent_node_id": self.data.parent_node_id,
|
||||
},
|
||||
}
|
||||
|
||||
@ -420,7 +420,7 @@ class NodeRetryStreamResponse(StreamResponse):
|
||||
files: Sequence[Mapping[str, Any]] | None = []
|
||||
iteration_id: str | None = None
|
||||
loop_id: str | None = None
|
||||
mention_parent_id: str | None = None
|
||||
parent_node_id: str | None = None
|
||||
retry_index: int = 0
|
||||
|
||||
event: StreamEvent = StreamEvent.NODE_RETRY
|
||||
@ -451,7 +451,7 @@ class NodeRetryStreamResponse(StreamResponse):
|
||||
"files": [],
|
||||
"iteration_id": self.data.iteration_id,
|
||||
"loop_id": self.data.loop_id,
|
||||
"mention_parent_id": self.data.mention_parent_id,
|
||||
"parent_node_id": self.data.parent_node_id,
|
||||
"retry_index": self.data.retry_index,
|
||||
},
|
||||
}
|
||||
|
||||
@ -813,7 +813,19 @@ Parameter: {parameter_info.get("name")} ({param_type}) - {parameter_info.get("de
|
||||
if isinstance(v, dict)
|
||||
]
|
||||
|
||||
outputs = content.get("outputs", {"result": {"type": parameter_type}})
|
||||
# Convert outputs from array format [{name, type}] to dict format {name: {type}}
|
||||
# Array format is required for OpenAI/Azure strict JSON schema compatibility
|
||||
raw_outputs = content.get("outputs", [])
|
||||
if isinstance(raw_outputs, list):
|
||||
outputs = {
|
||||
item.get("name", "result"): {"type": item.get("type", parameter_type)}
|
||||
for item in raw_outputs
|
||||
if isinstance(item, dict) and item.get("name")
|
||||
}
|
||||
if not outputs:
|
||||
outputs = {"result": {"type": parameter_type}}
|
||||
else:
|
||||
outputs = raw_outputs or {"result": {"type": parameter_type}}
|
||||
|
||||
return {
|
||||
"variables": variables,
|
||||
|
||||
@ -3,32 +3,65 @@ from __future__ import annotations
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from core.variables.types import SegmentType
|
||||
from core.workflow.nodes.base.entities import VariableSelector
|
||||
|
||||
|
||||
class SuggestedQuestionsOutput(BaseModel):
|
||||
"""Output model for suggested questions generation."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
questions: list[str] = Field(min_length=3, max_length=3)
|
||||
questions: list[str] = Field(
|
||||
min_length=3,
|
||||
max_length=3,
|
||||
description="Exactly 3 suggested follow-up questions for the user",
|
||||
)
|
||||
|
||||
|
||||
class CodeNodeOutput(BaseModel):
|
||||
class VariableSelectorOutput(BaseModel):
|
||||
"""Variable selector mapping code variable to upstream node output.
|
||||
|
||||
Note: Separate from VariableSelector to ensure 'additionalProperties: false'
|
||||
in JSON schema for OpenAI/Azure strict mode.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
type: SegmentType
|
||||
variable: str = Field(description="Variable name used in the generated code")
|
||||
value_selector: list[str] = Field(description="Path to upstream node output, format: [node_id, output_name]")
|
||||
|
||||
|
||||
class CodeNodeOutputItem(BaseModel):
|
||||
"""Single output variable definition.
|
||||
|
||||
Note: OpenAI/Azure strict mode requires 'additionalProperties: false' and
|
||||
does not support dynamic object keys, so outputs use array format.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(description="Output variable name returned by the main function")
|
||||
type: SegmentType = Field(description="Data type of the output variable")
|
||||
|
||||
|
||||
class CodeNodeStructuredOutput(BaseModel):
|
||||
"""Structured output for code node generation."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
variables: list[VariableSelector]
|
||||
code: str
|
||||
outputs: dict[str, CodeNodeOutput]
|
||||
explanation: str
|
||||
variables: list[VariableSelectorOutput] = Field(
|
||||
description="Input variables mapping code variables to upstream node outputs"
|
||||
)
|
||||
code: str = Field(description="Generated code with a main function that processes inputs and returns outputs")
|
||||
outputs: list[CodeNodeOutputItem] = Field(
|
||||
description="Output variable definitions specifying name and type for each return value"
|
||||
)
|
||||
explanation: str = Field(description="Brief explanation of what the generated code does")
|
||||
|
||||
|
||||
class InstructionModifyOutput(BaseModel):
|
||||
"""Output model for instruction-based prompt modification."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
modified: str
|
||||
message: str
|
||||
modified: str = Field(description="The modified prompt content after applying the instruction")
|
||||
message: str = Field(description="Brief explanation of what changes were made")
|
||||
|
||||
@ -1058,10 +1058,10 @@ class ToolManager:
|
||||
elif tool_input.type == "mixed":
|
||||
segment_group = variable_pool.convert_template(str(tool_input.value))
|
||||
parameter_value = segment_group.text
|
||||
elif tool_input.type == "mention":
|
||||
# Mention type not supported in agent mode
|
||||
elif tool_input.type == "nested_node":
|
||||
# Nested node type not supported in agent mode
|
||||
raise ToolParameterError(
|
||||
f"Mention type not supported in agent for parameter '{parameter.name}'"
|
||||
f"Nested node type not supported in agent for parameter '{parameter.name}'"
|
||||
)
|
||||
else:
|
||||
raise ToolParameterError(f"Unknown tool input type '{tool_input.type}'")
|
||||
|
||||
@ -256,7 +256,7 @@ class WorkflowNodeExecutionMetadataKey(StrEnum):
|
||||
LLM_CONTENT_SEQUENCE = "llm_content_sequence"
|
||||
LLM_TRACE = "llm_trace"
|
||||
COMPLETED_REASON = "completed_reason" # completed reason for loop node
|
||||
MENTION_PARENT_ID = "mention_parent_id" # parent node id for extractor nodes
|
||||
PARENT_NODE_ID = "parent_node_id" # parent node id for nested nodes (extractor nodes)
|
||||
|
||||
|
||||
class WorkflowNodeExecutionStatus(StrEnum):
|
||||
|
||||
@ -94,7 +94,7 @@ class EventHandler:
|
||||
event: The event to handle
|
||||
"""
|
||||
# Events in loops, iterations, or extractor groups are always collected
|
||||
if event.in_loop_id or event.in_iteration_id or event.in_mention_parent_id:
|
||||
if event.in_loop_id or event.in_iteration_id or event.in_parent_node_id:
|
||||
self._event_collector.collect(event)
|
||||
return
|
||||
return self._dispatch(event)
|
||||
|
||||
@ -68,7 +68,7 @@ class _NodeRuntimeSnapshot:
|
||||
predecessor_node_id: str | None
|
||||
iteration_id: str | None
|
||||
loop_id: str | None
|
||||
mention_parent_id: str | None
|
||||
parent_node_id: str | None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@ -231,7 +231,7 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
|
||||
metadata = {
|
||||
WorkflowNodeExecutionMetadataKey.ITERATION_ID: event.in_iteration_id,
|
||||
WorkflowNodeExecutionMetadataKey.LOOP_ID: event.in_loop_id,
|
||||
WorkflowNodeExecutionMetadataKey.MENTION_PARENT_ID: event.in_mention_parent_id,
|
||||
WorkflowNodeExecutionMetadataKey.PARENT_NODE_ID: event.in_parent_node_id,
|
||||
}
|
||||
|
||||
domain_execution = WorkflowNodeExecution(
|
||||
@ -258,7 +258,7 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
|
||||
predecessor_node_id=event.predecessor_node_id,
|
||||
iteration_id=event.in_iteration_id,
|
||||
loop_id=event.in_loop_id,
|
||||
mention_parent_id=event.in_mention_parent_id,
|
||||
parent_node_id=event.in_parent_node_id,
|
||||
created_at=event.start_at,
|
||||
)
|
||||
self._node_snapshots[event.id] = snapshot
|
||||
|
||||
@ -21,10 +21,10 @@ class GraphNodeEventBase(GraphEngineEvent):
|
||||
"""iteration id if node is in iteration"""
|
||||
in_loop_id: str | None = None
|
||||
"""loop id if node is in loop"""
|
||||
in_mention_parent_id: str | None = None
|
||||
"""Parent node id if this is an extractor node event.
|
||||
in_parent_node_id: str | None = None
|
||||
"""Parent node id if this is a nested node event.
|
||||
|
||||
When set, indicates this event belongs to an extractor node that
|
||||
When set, indicates this event belongs to a nested node that
|
||||
is extracting values for the specified parent node.
|
||||
"""
|
||||
|
||||
|
||||
@ -288,59 +288,45 @@ class Node(Generic[NodeDataT]):
|
||||
extractor_configs.append(node_config)
|
||||
return extractor_configs
|
||||
|
||||
def _execute_mention_nodes(self) -> Generator[GraphNodeEventBase, None, None]:
|
||||
def _execute_nested_nodes(self) -> Generator[GraphNodeEventBase, None, None]:
|
||||
"""
|
||||
Execute all extractor nodes associated with this node.
|
||||
Execute all nested nodes associated with this node.
|
||||
|
||||
Extractor nodes are nodes with parent_node_id == self._node_id.
|
||||
Nested nodes are nodes with parent_node_id == self._node_id.
|
||||
They are executed before the main node to extract values from list[PromptMessage].
|
||||
"""
|
||||
from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
|
||||
from core.workflow.nodes.node_factory import DifyNodeFactory
|
||||
|
||||
extractor_configs = self._find_extractor_node_configs()
|
||||
logger.debug("[Extractor] Found %d extractor nodes for parent '%s'", len(extractor_configs), self._node_id)
|
||||
logger.debug("[NestedNode] Found %d nested nodes for parent '%s'", len(extractor_configs), self._node_id)
|
||||
if not extractor_configs:
|
||||
return
|
||||
|
||||
# Use DifyNodeFactory to properly instantiate nodes with required dependencies
|
||||
node_factory = DifyNodeFactory(
|
||||
graph_init_params=self._graph_init_params,
|
||||
graph_runtime_state=self.graph_runtime_state,
|
||||
)
|
||||
|
||||
for config in extractor_configs:
|
||||
node_id = config.get("id")
|
||||
node_data = config.get("data", {})
|
||||
node_type_str = node_data.get("type")
|
||||
|
||||
if not node_id or not node_type_str:
|
||||
if not node_id:
|
||||
continue
|
||||
|
||||
# Get node class
|
||||
try:
|
||||
node_type = NodeType(node_type_str)
|
||||
nested_node = node_factory.create_node(config)
|
||||
except ValueError:
|
||||
# Skip nodes that cannot be created (e.g., unknown type)
|
||||
continue
|
||||
|
||||
node_mapping = NODE_TYPE_CLASSES_MAPPING.get(node_type)
|
||||
if not node_mapping:
|
||||
continue
|
||||
|
||||
node_version = str(node_data.get("version", "1"))
|
||||
node_cls = node_mapping.get(node_version) or node_mapping.get(LATEST_VERSION)
|
||||
if not node_cls:
|
||||
continue
|
||||
|
||||
# Instantiate and execute the extractor node
|
||||
extractor_node = node_cls(
|
||||
id=node_id,
|
||||
config=config,
|
||||
graph_init_params=self._graph_init_params,
|
||||
graph_runtime_state=self.graph_runtime_state,
|
||||
)
|
||||
|
||||
# Execute and process extractor node events
|
||||
for event in extractor_node.run():
|
||||
# Execute and process nested node events
|
||||
for event in nested_node.run():
|
||||
# Tag event with parent node id for stream ordering and history tracking
|
||||
if isinstance(event, GraphNodeEventBase):
|
||||
event.in_mention_parent_id = self._node_id
|
||||
event.in_parent_node_id = self._node_id
|
||||
|
||||
if isinstance(event, NodeRunSucceededEvent):
|
||||
# Store extractor node outputs in variable pool
|
||||
# Store nested node outputs in variable pool
|
||||
outputs: Mapping[str, Any] = event.node_run_result.outputs
|
||||
for variable_name, variable_value in outputs.items():
|
||||
self.graph_runtime_state.variable_pool.add((node_id, variable_name), variable_value)
|
||||
@ -351,8 +337,8 @@ class Node(Generic[NodeDataT]):
|
||||
execution_id = self.ensure_execution_id()
|
||||
self._start_at = naive_utc_now()
|
||||
|
||||
# Step 1: Execute associated extractor nodes before main node execution
|
||||
yield from self._execute_mention_nodes()
|
||||
# Step 1: Execute associated nested nodes before main node execution
|
||||
yield from self._execute_nested_nodes()
|
||||
|
||||
# Create and push start event with required fields
|
||||
start_event = NodeRunStartedEvent(
|
||||
|
||||
@ -8,17 +8,17 @@ from pydantic_core.core_schema import ValidationInfo
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.workflow.nodes.base.entities import BaseNodeData
|
||||
|
||||
# Pattern to match mention value format: {{@node.context@}}instruction
|
||||
# Pattern to match nested_node value format: {{@node.context@}}instruction
|
||||
# The placeholder {{@node.context@}} must appear at the beginning
|
||||
# Format: {{@agent_node_id.context@}} where agent_node_id is dynamic, context is fixed
|
||||
MENTION_VALUE_PATTERN = re.compile(r"^\{\{@([a-zA-Z0-9_]+)\.context@\}\}(.*)$", re.DOTALL)
|
||||
NESTED_NODE_VALUE_PATTERN = re.compile(r"^\{\{@([a-zA-Z0-9_]+)\.context@\}\}(.*)$", re.DOTALL)
|
||||
|
||||
|
||||
def parse_mention_value(value: str) -> tuple[str, str]:
|
||||
"""Parse mention value into (node_id, instruction).
|
||||
def parse_nested_node_value(value: str) -> tuple[str, str]:
|
||||
"""Parse nested_node value into (node_id, instruction).
|
||||
|
||||
Args:
|
||||
value: The mention value string like "{{@llm.context@}}extract keywords"
|
||||
value: The nested_node value string like "{{@llm.context@}}extract keywords"
|
||||
|
||||
Returns:
|
||||
Tuple of (node_id, instruction)
|
||||
@ -26,16 +26,16 @@ def parse_mention_value(value: str) -> tuple[str, str]:
|
||||
Raises:
|
||||
ValueError: If value format is invalid
|
||||
"""
|
||||
match = MENTION_VALUE_PATTERN.match(value)
|
||||
match = NESTED_NODE_VALUE_PATTERN.match(value)
|
||||
if not match:
|
||||
raise ValueError(
|
||||
"For mention type, value must start with {{@node.context@}} placeholder, "
|
||||
"For nested_node type, value must start with {{@node.context@}} placeholder, "
|
||||
"e.g., '{{@llm.context@}}extract keywords'"
|
||||
)
|
||||
return match.group(1), match.group(2)
|
||||
|
||||
|
||||
class MentionConfig(BaseModel):
|
||||
class NestedNodeConfig(BaseModel):
|
||||
"""Configuration for extracting value from context variable.
|
||||
|
||||
Used when a tool parameter needs to be extracted from list[PromptMessage]
|
||||
@ -87,9 +87,9 @@ class ToolNodeData(BaseNodeData, ToolEntity):
|
||||
class ToolInput(BaseModel):
|
||||
# TODO: check this type
|
||||
value: Union[Any, list[str]]
|
||||
type: Literal["mixed", "variable", "constant", "mention"]
|
||||
# Required config for mention type, extracting value from context variable
|
||||
mention_config: MentionConfig | None = None
|
||||
type: Literal["mixed", "variable", "constant", "nested_node"]
|
||||
# Required config for nested_node type, extracting value from context variable
|
||||
nested_node_config: NestedNodeConfig | None = None
|
||||
|
||||
@field_validator("type", mode="before")
|
||||
@classmethod
|
||||
@ -102,7 +102,7 @@ class ToolNodeData(BaseNodeData, ToolEntity):
|
||||
|
||||
if typ == "mixed" and not isinstance(value, str):
|
||||
raise ValueError("value must be a string")
|
||||
elif typ == "mention":
|
||||
elif typ == "nested_node":
|
||||
# Skip here, will be validated in model_validator
|
||||
pass
|
||||
elif typ == "variable":
|
||||
@ -116,9 +116,9 @@ class ToolNodeData(BaseNodeData, ToolEntity):
|
||||
return typ
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_mention_type(self) -> Self:
|
||||
"""Validate mention type with mention_config."""
|
||||
if self.type != "mention":
|
||||
def check_nested_node_type(self) -> Self:
|
||||
"""Validate nested_node type with nested_node_config."""
|
||||
if self.type != "nested_node":
|
||||
return self
|
||||
|
||||
value = self.value
|
||||
@ -126,13 +126,13 @@ class ToolNodeData(BaseNodeData, ToolEntity):
|
||||
return self
|
||||
|
||||
if not isinstance(value, str):
|
||||
raise ValueError("value must be a string for mention type")
|
||||
# For mention type, value must match format: {{@node.context@}}instruction
|
||||
raise ValueError("value must be a string for nested_node type")
|
||||
# For nested_node type, value must match format: {{@node.context@}}instruction
|
||||
# This will raise ValueError if format is invalid
|
||||
parse_mention_value(value)
|
||||
# mention_config is required for mention type
|
||||
if self.mention_config is None:
|
||||
raise ValueError("mention_config is required for mention type")
|
||||
parse_nested_node_value(value)
|
||||
# nested_node_config is required for nested_node type
|
||||
if self.nested_node_config is None:
|
||||
raise ValueError("nested_node_config is required for nested_node type")
|
||||
return self
|
||||
|
||||
tool_parameters: dict[str, ToolInput]
|
||||
|
||||
@ -212,16 +212,16 @@ class ToolNode(Node[ToolNodeData]):
|
||||
raise ToolParameterError(f"Variable {selector} does not exist")
|
||||
continue
|
||||
parameter_value = variable.value
|
||||
elif tool_input.type == "mention":
|
||||
# Mention type: get value from extractor node's output
|
||||
if tool_input.mention_config is None:
|
||||
elif tool_input.type == "nested_node":
|
||||
# Nested node type: get value from extractor node's output
|
||||
if tool_input.nested_node_config is None:
|
||||
raise ToolParameterError(
|
||||
f"mention_config is required for mention type parameter '{parameter_name}'"
|
||||
f"nested_node_config is required for nested_node type parameter '{parameter_name}'"
|
||||
)
|
||||
mention_config = tool_input.mention_config.model_dump()
|
||||
nested_node_config = tool_input.nested_node_config.model_dump()
|
||||
try:
|
||||
parameter_value, found = variable_pool.resolve_mention(
|
||||
mention_config, parameter_name=parameter_name
|
||||
parameter_value, found = variable_pool.resolve_nested_node(
|
||||
nested_node_config, parameter_name=parameter_name
|
||||
)
|
||||
if not found and parameter.required:
|
||||
raise ToolParameterError(
|
||||
@ -518,8 +518,8 @@ class ToolNode(Node[ToolNodeData]):
|
||||
if isinstance(input.value, list):
|
||||
selector_key = ".".join(input.value)
|
||||
result[f"#{selector_key}#"] = input.value
|
||||
elif input.type == "mention":
|
||||
# Mention type: value is handled by extractor node, no direct variable reference
|
||||
elif input.type == "nested_node":
|
||||
# Nested node type: value is handled by extractor node, no direct variable reference
|
||||
pass
|
||||
elif input.type == "constant":
|
||||
pass
|
||||
|
||||
@ -79,8 +79,7 @@ class ReadOnlyGraphRuntimeState(Protocol):
|
||||
...
|
||||
|
||||
@property
|
||||
def sandbox(self) -> Any:
|
||||
...
|
||||
def sandbox(self) -> Any: ...
|
||||
|
||||
def dumps(self) -> str:
|
||||
"""Serialize the runtime state into a JSON snapshot (read-only)."""
|
||||
|
||||
@ -268,21 +268,21 @@ class VariablePool(BaseModel):
|
||||
continue
|
||||
self.add(selector, value)
|
||||
|
||||
def resolve_mention(
|
||||
def resolve_nested_node(
|
||||
self,
|
||||
mention_config: Mapping[str, Any],
|
||||
nested_node_config: Mapping[str, Any],
|
||||
/,
|
||||
*,
|
||||
parameter_name: str = "",
|
||||
) -> tuple[Any, bool]:
|
||||
"""
|
||||
Resolve a mention parameter value from an extractor node's output.
|
||||
Resolve a nested_node parameter value from an extractor node's output.
|
||||
|
||||
Mention parameters reference values extracted by an extractor LLM node
|
||||
Nested node parameters reference values extracted by an extractor LLM node
|
||||
from list[PromptMessage] context.
|
||||
|
||||
Args:
|
||||
mention_config: A dict containing:
|
||||
nested_node_config: A dict containing:
|
||||
- extractor_node_id: ID of the extractor LLM node
|
||||
- output_selector: Selector path for the output variable (e.g., ["text"])
|
||||
- null_strategy: "raise_error" or "use_default"
|
||||
@ -298,13 +298,13 @@ class VariablePool(BaseModel):
|
||||
ValueError: If extractor_node_id is missing, or if null_strategy is
|
||||
"raise_error" and the value is not found
|
||||
"""
|
||||
extractor_node_id = mention_config.get("extractor_node_id")
|
||||
extractor_node_id = nested_node_config.get("extractor_node_id")
|
||||
if not extractor_node_id:
|
||||
raise ValueError(f"Missing extractor_node_id for mention parameter '{parameter_name}'")
|
||||
raise ValueError(f"Missing extractor_node_id for nested_node parameter '{parameter_name}'")
|
||||
|
||||
output_selector = list(mention_config.get("output_selector", []))
|
||||
null_strategy = mention_config.get("null_strategy", "raise_error")
|
||||
default_value = mention_config.get("default_value")
|
||||
output_selector = list(nested_node_config.get("output_selector", []))
|
||||
null_strategy = nested_node_config.get("null_strategy", "raise_error")
|
||||
default_value = nested_node_config.get("default_value")
|
||||
|
||||
# Build full selector: [extractor_node_id, ...output_selector]
|
||||
full_selector = [extractor_node_id] + output_selector
|
||||
|
||||
@ -165,27 +165,27 @@ class WorkflowScheduleCFSPlanEntity(BaseModel):
|
||||
granularity: int = Field(default=-1) # -1 means infinite
|
||||
|
||||
|
||||
# ========== Mention Graph Entities ==========
|
||||
# ========== Nested Node Graph Entities ==========
|
||||
|
||||
|
||||
class MentionParameterSchema(BaseModel):
|
||||
"""Schema for the parameter to be extracted from mention context."""
|
||||
class NestedNodeParameterSchema(BaseModel):
|
||||
"""Schema for the parameter to be extracted from nested node context."""
|
||||
|
||||
name: str = Field(description="Parameter name (e.g., 'query')")
|
||||
type: str = Field(default="string", description="Parameter type (e.g., 'string', 'number')")
|
||||
description: str = Field(default="", description="Parameter description for LLM")
|
||||
|
||||
|
||||
class MentionGraphRequest(BaseModel):
|
||||
"""Request payload for generating mention graph."""
|
||||
class NestedNodeGraphRequest(BaseModel):
|
||||
"""Request payload for generating nested node graph."""
|
||||
|
||||
parent_node_id: str = Field(description="ID of the parent node that uses the extracted value")
|
||||
parameter_key: str = Field(description="Key of the parameter being extracted")
|
||||
context_source: list[str] = Field(description="Variable selector for the context source")
|
||||
parameter_schema: MentionParameterSchema = Field(description="Schema of the parameter to extract")
|
||||
parameter_schema: NestedNodeParameterSchema = Field(description="Schema of the parameter to extract")
|
||||
|
||||
|
||||
class MentionGraphResponse(BaseModel):
|
||||
"""Response containing the generated mention graph."""
|
||||
class NestedNodeGraphResponse(BaseModel):
|
||||
"""Response containing the generated nested node graph."""
|
||||
|
||||
graph: Mapping[str, Any] = Field(description="Complete graph structure with nodes, edges, viewport")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
"""
|
||||
Service for generating Mention LLM node graph structures.
|
||||
Service for generating Nested Node LLM graph structures.
|
||||
|
||||
This service creates graph structures containing LLM nodes configured for
|
||||
extracting values from list[PromptMessage] variables.
|
||||
@ -12,35 +12,35 @@ from sqlalchemy.orm import Session
|
||||
from core.model_runtime.entities import LLMMode
|
||||
from core.workflow.enums import NodeType
|
||||
from services.model_provider_service import ModelProviderService
|
||||
from services.workflow.entities import MentionGraphRequest, MentionGraphResponse, MentionParameterSchema
|
||||
from services.workflow.entities import NestedNodeGraphRequest, NestedNodeGraphResponse, NestedNodeParameterSchema
|
||||
|
||||
|
||||
class MentionGraphService:
|
||||
"""Service for generating Mention LLM node graph structures."""
|
||||
class NestedNodeGraphService:
|
||||
"""Service for generating Nested Node LLM graph structures."""
|
||||
|
||||
def __init__(self, session: Session):
|
||||
self._session = session
|
||||
|
||||
def generate_mention_node_id(self, node_id: str, parameter_name: str) -> str:
|
||||
"""Generate mention node ID following the naming convention.
|
||||
def generate_nested_node_id(self, node_id: str, parameter_name: str) -> str:
|
||||
"""Generate nested node ID following the naming convention.
|
||||
|
||||
Format: {node_id}_ext_{parameter_name}
|
||||
"""
|
||||
return f"{node_id}_ext_{parameter_name}"
|
||||
|
||||
def generate_mention_graph(self, tenant_id: str, request: MentionGraphRequest) -> MentionGraphResponse:
|
||||
"""Generate a complete graph structure containing a Mention LLM node.
|
||||
def generate_nested_node_graph(self, tenant_id: str, request: NestedNodeGraphRequest) -> NestedNodeGraphResponse:
|
||||
"""Generate a complete graph structure containing a Nested Node LLM node.
|
||||
|
||||
Args:
|
||||
tenant_id: The tenant ID for fetching default model config
|
||||
request: The mention graph generation request
|
||||
request: The nested node graph generation request
|
||||
|
||||
Returns:
|
||||
Complete graph structure with nodes, edges, and viewport
|
||||
"""
|
||||
node_id = self.generate_mention_node_id(request.parent_node_id, request.parameter_key)
|
||||
node_id = self.generate_nested_node_id(request.parent_node_id, request.parameter_key)
|
||||
model_config = self._get_default_model_config(tenant_id)
|
||||
node = self._build_mention_llm_node(
|
||||
node = self._build_nested_node_llm_node(
|
||||
node_id=node_id,
|
||||
parent_node_id=request.parent_node_id,
|
||||
context_source=request.context_source,
|
||||
@ -54,7 +54,7 @@ class MentionGraphService:
|
||||
"viewport": {},
|
||||
}
|
||||
|
||||
return MentionGraphResponse(graph=graph)
|
||||
return NestedNodeGraphResponse(graph=graph)
|
||||
|
||||
def _get_default_model_config(self, tenant_id: str) -> dict[str, Any]:
|
||||
"""Get the default LLM model configuration for the tenant."""
|
||||
@ -80,16 +80,16 @@ class MentionGraphService:
|
||||
"completion_params": {},
|
||||
}
|
||||
|
||||
def _build_mention_llm_node(
|
||||
def _build_nested_node_llm_node(
|
||||
self,
|
||||
*,
|
||||
node_id: str,
|
||||
parent_node_id: str,
|
||||
context_source: list[str],
|
||||
parameter_schema: MentionParameterSchema,
|
||||
parameter_schema: NestedNodeParameterSchema,
|
||||
model_config: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Build the Mention LLM node structure.
|
||||
"""Build the Nested Node LLM node structure.
|
||||
|
||||
The node uses:
|
||||
- $context in prompt_template to reference the PromptMessage list
|
||||
@ -124,7 +124,7 @@ class MentionGraphService:
|
||||
"position": {"x": 0, "y": 0},
|
||||
"data": {
|
||||
"type": NodeType.LLM.value,
|
||||
"title": f"Mention: {parameter_schema.name}",
|
||||
"title": f"NestedNode: {parameter_schema.name}",
|
||||
"desc": f"Extract {parameter_schema.name} from conversation context",
|
||||
"parent_node_id": parent_node_id,
|
||||
"model": model_config,
|
||||
4
api/tests/fixtures/pav-test-extraction.yml
vendored
4
api/tests/fixtures/pav-test-extraction.yml
vendored
@ -207,9 +207,9 @@ workflow:
|
||||
tool_node_version: '2'
|
||||
tool_parameters:
|
||||
query:
|
||||
type: mention
|
||||
type: nested_node
|
||||
value: '{{@llm.context@}}请从对话历史中提取用户想要搜索的关键词,只返回关键词本身'
|
||||
mention_config:
|
||||
nested_node_config:
|
||||
extractor_node_id: 1767773709491_ext_query
|
||||
output_selector:
|
||||
- structured_output
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { RiCheckLine } from '@remixicon/react'
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
@ -17,43 +17,43 @@ import { cn } from '@/utils/classnames'
|
||||
type ConfigPanelProps = {
|
||||
agentName: string
|
||||
extractorNodeId: string
|
||||
mentionConfig: MentionConfig
|
||||
nestedNodeConfig: NestedNodeConfig
|
||||
availableNodes: Node[]
|
||||
availableVars: NodeOutPutVar[]
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
onNestedNodeConfigChange: (config: NestedNodeConfig) => void
|
||||
}
|
||||
|
||||
const ConfigPanel: FC<ConfigPanelProps> = ({
|
||||
agentName,
|
||||
extractorNodeId,
|
||||
mentionConfig,
|
||||
nestedNodeConfig,
|
||||
availableNodes,
|
||||
availableVars,
|
||||
onMentionConfigChange,
|
||||
onNestedNodeConfigChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [tabType, setTabType] = useState<TabType>(TabType.settings)
|
||||
|
||||
const resolvedExtractorId = mentionConfig.extractor_node_id || extractorNodeId
|
||||
const resolvedExtractorId = nestedNodeConfig.extractor_node_id || extractorNodeId
|
||||
|
||||
const selectedOutput = useMemo<ValueSelector>(() => {
|
||||
if (!resolvedExtractorId || !mentionConfig.output_selector?.length)
|
||||
if (!resolvedExtractorId || !nestedNodeConfig.output_selector?.length)
|
||||
return []
|
||||
|
||||
return [resolvedExtractorId, ...(mentionConfig.output_selector || [])]
|
||||
}, [mentionConfig.output_selector, resolvedExtractorId])
|
||||
return [resolvedExtractorId, ...(nestedNodeConfig.output_selector || [])]
|
||||
}, [nestedNodeConfig.output_selector, resolvedExtractorId])
|
||||
|
||||
const handleOutputVarChange = useCallback((value: ValueSelector | string) => {
|
||||
const selector = Array.isArray(value) ? value : []
|
||||
const nextExtractorId = selector[0] || resolvedExtractorId
|
||||
const nextOutputSelector = selector.length > 1 ? selector.slice(1) : []
|
||||
|
||||
onMentionConfigChange({
|
||||
...mentionConfig,
|
||||
onNestedNodeConfigChange({
|
||||
...nestedNodeConfig,
|
||||
extractor_node_id: nextExtractorId,
|
||||
output_selector: nextOutputSelector,
|
||||
})
|
||||
}, [mentionConfig, onMentionConfigChange, resolvedExtractorId])
|
||||
}, [nestedNodeConfig, onNestedNodeConfigChange, resolvedExtractorId])
|
||||
|
||||
const whenOutputNoneOptions = useMemo(() => ([
|
||||
{
|
||||
@ -68,17 +68,17 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
|
||||
},
|
||||
]), [t])
|
||||
const selectedWhenOutputNoneOption = useMemo(() => (
|
||||
whenOutputNoneOptions.find(item => item.value === mentionConfig.null_strategy) ?? whenOutputNoneOptions[0]
|
||||
), [mentionConfig.null_strategy, whenOutputNoneOptions])
|
||||
whenOutputNoneOptions.find(item => item.value === nestedNodeConfig.null_strategy) ?? whenOutputNoneOptions[0]
|
||||
), [nestedNodeConfig.null_strategy, whenOutputNoneOptions])
|
||||
|
||||
const handleNullStrategyChange = useCallback((item: Item) => {
|
||||
if (typeof item.value !== 'string')
|
||||
return
|
||||
onMentionConfigChange({
|
||||
...mentionConfig,
|
||||
null_strategy: item.value as MentionConfig['null_strategy'],
|
||||
onNestedNodeConfigChange({
|
||||
...nestedNodeConfig,
|
||||
null_strategy: item.value as NestedNodeConfig['null_strategy'],
|
||||
})
|
||||
}, [mentionConfig, onMentionConfigChange])
|
||||
}, [nestedNodeConfig, onNestedNodeConfigChange])
|
||||
|
||||
const handleDefaultValueChange = useCallback((value: string) => {
|
||||
const trimmed = value.trim()
|
||||
@ -92,12 +92,12 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
onMentionConfigChange({
|
||||
...mentionConfig,
|
||||
onNestedNodeConfigChange({
|
||||
...nestedNodeConfig,
|
||||
default_value: nextValue,
|
||||
})
|
||||
}, [mentionConfig, onMentionConfigChange])
|
||||
const defaultValue = mentionConfig.default_value ?? ''
|
||||
}, [nestedNodeConfig, onNestedNodeConfigChange])
|
||||
const defaultValue = nestedNodeConfig.default_value ?? ''
|
||||
const shouldFormatDefaultValue = typeof defaultValue !== 'string'
|
||||
|
||||
return (
|
||||
@ -142,7 +142,7 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
|
||||
<div className="flex items-center">
|
||||
<SimpleSelect
|
||||
items={whenOutputNoneOptions}
|
||||
defaultValue={mentionConfig.null_strategy}
|
||||
defaultValue={nestedNodeConfig.null_strategy}
|
||||
allowSearch={false}
|
||||
notClearable
|
||||
wrapperClassName="min-w-[160px]"
|
||||
@ -170,7 +170,7 @@ const ConfigPanel: FC<ConfigPanelProps> = ({
|
||||
{selectedWhenOutputNoneOption.description}
|
||||
</div>
|
||||
)}
|
||||
{mentionConfig.null_strategy === 'use_default' && (
|
||||
{nestedNodeConfig.null_strategy === 'use_default' && (
|
||||
<div className={cn('overflow-hidden rounded-lg border border-components-input-border-active bg-components-input-bg-normal p-1')}>
|
||||
<CodeEditor
|
||||
noWrapper
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useStore as useReactFlowStore } from 'reactflow'
|
||||
@ -14,8 +14,8 @@ type SubGraphChildrenProps
|
||||
variant: 'agent'
|
||||
title: string
|
||||
extractorNodeId: string
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
nestedNodeConfig: NestedNodeConfig
|
||||
onNestedNodeConfigChange: (config: NestedNodeConfig) => void
|
||||
}
|
||||
| {
|
||||
variant: 'assemble'
|
||||
@ -72,10 +72,10 @@ const SubGraphChildren: FC<SubGraphChildrenProps> = (props) => {
|
||||
<ConfigPanel
|
||||
agentName={title}
|
||||
extractorNodeId={extractorNodeId}
|
||||
mentionConfig={agentProps.mentionConfig}
|
||||
nestedNodeConfig={agentProps.nestedNodeConfig}
|
||||
availableNodes={availableNodes}
|
||||
availableVars={availableVars}
|
||||
onMentionConfigChange={agentProps.onMentionConfigChange}
|
||||
onNestedNodeConfigChange={agentProps.onNestedNodeConfigChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
||||
import type { Viewport } from 'reactflow'
|
||||
import type { SyncWorkflowDraft, SyncWorkflowDraftCallback } from '../types'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
@ -29,8 +29,8 @@ type SubGraphMainBaseProps = {
|
||||
type SubGraphMainProps
|
||||
= | (SubGraphMainBaseProps & {
|
||||
variant: 'agent'
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
nestedNodeConfig: NestedNodeConfig
|
||||
onNestedNodeConfigChange: (config: NestedNodeConfig) => void
|
||||
})
|
||||
| (SubGraphMainBaseProps & {
|
||||
variant: 'assemble'
|
||||
@ -110,8 +110,8 @@ const SubGraphMain: FC<SubGraphMainProps> = (props) => {
|
||||
variant="agent"
|
||||
title={title}
|
||||
extractorNodeId={extractorNodeId}
|
||||
mentionConfig={props.mentionConfig}
|
||||
onMentionConfigChange={props.onMentionConfigChange}
|
||||
nestedNodeConfig={props.nestedNodeConfig}
|
||||
onNestedNodeConfigChange={props.onNestedNodeConfigChange}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
|
||||
@ -230,8 +230,8 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
|
||||
title={sourceTitle}
|
||||
extractorNodeId={`${toolNodeId}_ext_${paramKey}`}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={props.mentionConfig}
|
||||
onMentionConfigChange={props.onMentionConfigChange}
|
||||
nestedNodeConfig={props.nestedNodeConfig}
|
||||
onNestedNodeConfigChange={props.onNestedNodeConfigChange}
|
||||
selectableNodeTypes={selectableNodeTypes}
|
||||
onSave={onSave}
|
||||
onSyncWorkflowDraft={onSyncWorkflowDraft}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { BlockEnum, Edge, Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
|
||||
@ -35,8 +35,8 @@ export type AgentSubGraphProps = BaseSubGraphProps & {
|
||||
sourceVariable: ValueSelector
|
||||
agentNodeId: string
|
||||
agentName: string
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
nestedNodeConfig: NestedNodeConfig
|
||||
onNestedNodeConfigChange: (config: NestedNodeConfig) => void
|
||||
extractorNode?: Node<LLMNodeType>
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { MentionConfig, ResourceVarInputs } from '../types'
|
||||
import type { NestedNodeConfig, ResourceVarInputs } from '../types'
|
||||
import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Event, Tool } from '@/app/components/tools/types'
|
||||
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||
@ -319,7 +319,7 @@ const FormInputItem: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const handleValueChange = (newValue: any, newType?: VarKindType, mentionConfig?: MentionConfig | null) => {
|
||||
const handleValueChange = (newValue: any, newType?: VarKindType, nestedNodeConfig?: NestedNodeConfig | null) => {
|
||||
const normalizedValue = isNumber ? Number.parseFloat(newValue) : newValue
|
||||
const assemblePlaceholder = nodeId && variable
|
||||
? `{{#${nodeId}_ext_${variable}.result#}}`
|
||||
@ -329,9 +329,9 @@ const FormInputItem: FC<Props> = ({
|
||||
&& normalizedValue.includes(assemblePlaceholder)
|
||||
const resolvedType = isAssembleValue
|
||||
? VarKindType.mixed
|
||||
: newType ?? (varInput?.type === VarKindType.mention ? VarKindType.mention : getVarKindType())
|
||||
const resolvedMentionConfig = resolvedType === VarKindType.mention
|
||||
? (mentionConfig ?? varInput?.mention_config ?? {
|
||||
: newType ?? (varInput?.type === VarKindType.nested_node ? VarKindType.nested_node : getVarKindType())
|
||||
const resolvedNestedNodeConfig = resolvedType === VarKindType.nested_node
|
||||
? (nestedNodeConfig ?? varInput?.nested_node_config ?? {
|
||||
extractor_node_id: '',
|
||||
output_selector: [],
|
||||
null_strategy: 'use_default',
|
||||
@ -345,7 +345,7 @@ const FormInputItem: FC<Props> = ({
|
||||
...varInput,
|
||||
type: resolvedType,
|
||||
value: normalizedValue,
|
||||
mention_config: resolvedMentionConfig,
|
||||
nested_node_config: resolvedNestedNodeConfig,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@ export enum VarKindType {
|
||||
variable = 'variable',
|
||||
constant = 'constant',
|
||||
mixed = 'mixed',
|
||||
mention = 'mention',
|
||||
nested_node = 'nested_node',
|
||||
}
|
||||
|
||||
export type MentionConfig = {
|
||||
export type NestedNodeConfig = {
|
||||
extractor_node_id: string
|
||||
output_selector: ValueSelector
|
||||
null_strategy: 'raise_error' | 'use_default'
|
||||
@ -19,7 +19,7 @@ export type MentionConfig = {
|
||||
export type ResourceVarInputs = Record<string, {
|
||||
type: VarKindType
|
||||
value?: string | ValueSelector | any
|
||||
mention_config?: MentionConfig
|
||||
nested_node_config?: NestedNodeConfig
|
||||
}>
|
||||
|
||||
// Base resource interface
|
||||
|
||||
@ -21,8 +21,8 @@ export type ContextGenerateChatMessage = ContextGenerateMessage & {
|
||||
|
||||
const defaultCompletionParams: CompletionParams = {
|
||||
temperature: 0.7,
|
||||
max_tokens: 0,
|
||||
top_p: 0,
|
||||
max_tokens: 4096,
|
||||
top_p: 0.1,
|
||||
echo: false,
|
||||
stop: [],
|
||||
presence_penalty: 0,
|
||||
|
||||
@ -12,7 +12,7 @@ import { useCallback, useMemo } from 'react'
|
||||
import { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types'
|
||||
import { generateNewNode, getNodeCustomTypeByNodeDataType, mergeNodeDefaultData } from '@/app/components/workflow/utils'
|
||||
import { fetchMentionGraph } from '@/service/workflow'
|
||||
import { fetchNestedNodeGraph } from '@/service/workflow'
|
||||
import { FlowType } from '@/types/common'
|
||||
|
||||
// Constants
|
||||
@ -160,7 +160,7 @@ export function useMixedVariableExtractor({
|
||||
return `${toolNodeId}_ext_${paramKey}`
|
||||
}, [paramKey, toolNodeId])
|
||||
|
||||
const resolveMentionParameterSchema = useCallback((key: string) => {
|
||||
const resolveNestedNodeParameterSchema = useCallback((key: string) => {
|
||||
if (!toolNodeId) {
|
||||
return {
|
||||
name: key,
|
||||
@ -337,37 +337,37 @@ export function useMixedVariableExtractor({
|
||||
handleSyncWorkflowDraft()
|
||||
}, [handleSyncWorkflowDraft, paramKey, reactFlowStore, toolNodeId])
|
||||
|
||||
const applyMentionGraphNodeData = useCallback((payload: {
|
||||
const applyNestedNodeGraphData = useCallback((payload: {
|
||||
extractorNodeId: string
|
||||
mentionNodeData: Partial<LLMNodeType>
|
||||
nestedNodeData: Partial<LLMNodeType>
|
||||
valueText: string
|
||||
detectAgentFromText: (text: string) => DetectedAgent | null
|
||||
}) => {
|
||||
const { extractorNodeId, mentionNodeData, valueText, detectAgentFromText } = payload
|
||||
const { extractorNodeId, nestedNodeData, valueText, detectAgentFromText } = payload
|
||||
if (!toolNodeId)
|
||||
return
|
||||
const hasPromptTemplate = Array.isArray(mentionNodeData.prompt_template)
|
||||
? mentionNodeData.prompt_template.length > 0
|
||||
: Boolean(mentionNodeData.prompt_template)
|
||||
const hasPromptTemplate = Array.isArray(nestedNodeData.prompt_template)
|
||||
? nestedNodeData.prompt_template.length > 0
|
||||
: Boolean(nestedNodeData.prompt_template)
|
||||
const nextData: Partial<LLMNodeType> = {}
|
||||
if (mentionNodeData.title)
|
||||
nextData.title = mentionNodeData.title
|
||||
if (mentionNodeData.desc)
|
||||
nextData.desc = mentionNodeData.desc
|
||||
if (mentionNodeData.model && (mentionNodeData.model.provider || mentionNodeData.model.name))
|
||||
nextData.model = mentionNodeData.model
|
||||
if (nestedNodeData.title)
|
||||
nextData.title = nestedNodeData.title
|
||||
if (nestedNodeData.desc)
|
||||
nextData.desc = nestedNodeData.desc
|
||||
if (nestedNodeData.model && (nestedNodeData.model.provider || nestedNodeData.model.name))
|
||||
nextData.model = nestedNodeData.model
|
||||
if (hasPromptTemplate)
|
||||
nextData.prompt_template = mentionNodeData.prompt_template
|
||||
if (typeof mentionNodeData.structured_output_enabled === 'boolean')
|
||||
nextData.structured_output_enabled = mentionNodeData.structured_output_enabled
|
||||
if (mentionNodeData.structured_output?.schema)
|
||||
nextData.structured_output = mentionNodeData.structured_output
|
||||
if (mentionNodeData.context)
|
||||
nextData.context = mentionNodeData.context
|
||||
if (mentionNodeData.vision)
|
||||
nextData.vision = mentionNodeData.vision
|
||||
if (Object.prototype.hasOwnProperty.call(mentionNodeData, 'memory'))
|
||||
nextData.memory = mentionNodeData.memory
|
||||
nextData.prompt_template = nestedNodeData.prompt_template
|
||||
if (typeof nestedNodeData.structured_output_enabled === 'boolean')
|
||||
nextData.structured_output_enabled = nestedNodeData.structured_output_enabled
|
||||
if (nestedNodeData.structured_output?.schema)
|
||||
nextData.structured_output = nestedNodeData.structured_output
|
||||
if (nestedNodeData.context)
|
||||
nextData.context = nestedNodeData.context
|
||||
if (nestedNodeData.vision)
|
||||
nextData.vision = nestedNodeData.vision
|
||||
if (Object.prototype.hasOwnProperty.call(nestedNodeData, 'memory'))
|
||||
nextData.memory = nestedNodeData.memory
|
||||
|
||||
if (Object.keys(nextData).length === 0)
|
||||
return
|
||||
@ -396,7 +396,7 @@ export function useMixedVariableExtractor({
|
||||
syncExtractorPromptFromText(valueText, detectAgentFromText)
|
||||
}, [handleSyncWorkflowDraft, reactFlowStore, syncExtractorPromptFromText, toolNodeId])
|
||||
|
||||
const requestMentionGraph = useCallback(async (payload: {
|
||||
const requestNestedNodeGraph = useCallback(async (payload: {
|
||||
agentId: string
|
||||
extractorNodeId: string
|
||||
valueText: string
|
||||
@ -406,28 +406,28 @@ export function useMixedVariableExtractor({
|
||||
return
|
||||
if (!configsMap?.flowId || configsMap.flowType !== FlowType.appFlow)
|
||||
return
|
||||
const parameterSchema = resolveMentionParameterSchema(paramKey)
|
||||
const parameterSchema = resolveNestedNodeParameterSchema(paramKey)
|
||||
try {
|
||||
const response = await fetchMentionGraph(configsMap.flowType, configsMap.flowId, {
|
||||
const response = await fetchNestedNodeGraph(configsMap.flowType, configsMap.flowId, {
|
||||
parent_node_id: toolNodeId,
|
||||
parameter_key: paramKey,
|
||||
context_source: [payload.agentId, 'context'],
|
||||
parameter_schema: parameterSchema,
|
||||
})
|
||||
const mentionNode = response?.graph?.nodes?.find(node => node.id === payload.extractorNodeId)
|
||||
const mentionNodeData = mentionNode?.data as Partial<LLMNodeType> | undefined
|
||||
if (!mentionNodeData)
|
||||
const nestedNode = response?.graph?.nodes?.find(node => node.id === payload.extractorNodeId)
|
||||
const nestedNodeData = nestedNode?.data as Partial<LLMNodeType> | undefined
|
||||
if (!nestedNodeData)
|
||||
return
|
||||
applyMentionGraphNodeData({
|
||||
applyNestedNodeGraphData({
|
||||
extractorNodeId: payload.extractorNodeId,
|
||||
mentionNodeData,
|
||||
nestedNodeData,
|
||||
valueText: payload.valueText,
|
||||
detectAgentFromText: payload.detectAgentFromText,
|
||||
})
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}, [applyMentionGraphNodeData, configsMap?.flowId, configsMap?.flowType, paramKey, resolveMentionParameterSchema, toolNodeId])
|
||||
}, [applyNestedNodeGraphData, configsMap?.flowId, configsMap?.flowType, paramKey, resolveNestedNodeParameterSchema, toolNodeId])
|
||||
|
||||
return {
|
||||
assembleExtractorNodeId,
|
||||
@ -435,6 +435,6 @@ export function useMixedVariableExtractor({
|
||||
ensureAssembleExtractorNode,
|
||||
removeExtractorNode,
|
||||
syncExtractorPromptFromText,
|
||||
requestMentionGraph,
|
||||
requestNestedNodeGraph,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ContextGenerateModalHandle } from '../context-generate-modal'
|
||||
import type { DetectedAgent } from './hooks'
|
||||
import type { AgentNode, WorkflowVariableBlockType } from '@/app/components/base/prompt-editor/types'
|
||||
import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types'
|
||||
import type { MentionConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { AgentNodeType } from '@/app/components/workflow/nodes/agent/types'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
@ -41,7 +41,7 @@ import {
|
||||
|
||||
type WorkflowNodesMap = NonNullable<WorkflowVariableBlockType['workflowNodesMap']>
|
||||
|
||||
const DEFAULT_MENTION_CONFIG: MentionConfig = {
|
||||
const DEFAULT_NESTED_NODE_CONFIG: NestedNodeConfig = {
|
||||
extractor_node_id: '',
|
||||
output_selector: [],
|
||||
null_strategy: 'use_default',
|
||||
@ -60,7 +60,7 @@ type MixedVariableTextInputProps = {
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: WorkflowNode[]
|
||||
value?: string
|
||||
onChange?: (text: string, type?: VarKindType, mentionConfig?: MentionConfig | null) => void
|
||||
onChange?: (text: string, type?: VarKindType, nestedNodeConfig?: NestedNodeConfig | null) => void
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
disableVariableInsertion?: boolean
|
||||
@ -134,7 +134,7 @@ const MixedVariableTextInput = ({
|
||||
ensureAssembleExtractorNode,
|
||||
removeExtractorNode,
|
||||
syncExtractorPromptFromText,
|
||||
requestMentionGraph,
|
||||
requestNestedNodeGraph,
|
||||
} = useMixedVariableExtractor({
|
||||
toolNodeId,
|
||||
paramKey,
|
||||
@ -297,22 +297,22 @@ const MixedVariableTextInput = ({
|
||||
})
|
||||
}
|
||||
|
||||
const mentionConfigWithOutputSelector: MentionConfig = {
|
||||
...DEFAULT_MENTION_CONFIG,
|
||||
const nestedNodeConfigWithOutputSelector: NestedNodeConfig = {
|
||||
...DEFAULT_NESTED_NODE_CONFIG,
|
||||
extractor_node_id: extractorNodeId,
|
||||
output_selector: paramKey ? ['structured_output', paramKey] : [],
|
||||
}
|
||||
onChange(newValue, VarKindTypeEnum.mention, mentionConfigWithOutputSelector)
|
||||
onChange(newValue, VarKindTypeEnum.nested_node, nestedNodeConfigWithOutputSelector)
|
||||
syncExtractorPromptFromText(newValue, detectAgentFromText)
|
||||
if (extractorNodeId) {
|
||||
void requestMentionGraph({
|
||||
void requestNestedNodeGraph({
|
||||
agentId: agent.id,
|
||||
extractorNodeId,
|
||||
valueText: newValue,
|
||||
detectAgentFromText,
|
||||
})
|
||||
}
|
||||
}, [detectAgentFromText, ensureExtractorNode, onChange, paramKey, requestMentionGraph, syncExtractorPromptFromText, toolNodeId, value])
|
||||
}, [detectAgentFromText, ensureExtractorNode, onChange, paramKey, requestNestedNodeGraph, syncExtractorPromptFromText, toolNodeId, value])
|
||||
|
||||
const handleAssembleSelect = useCallback((): ValueSelector | null => {
|
||||
if (!toolNodeId || !paramKey || !assemblePlaceholder)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { SubGraphModalProps } from './types'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { ToolNodeType } from '@/app/components/workflow/nodes/tool/types'
|
||||
@ -88,8 +88,8 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
return vars.filter(nodeVar => availableNodeIds.has(nodeVar.nodeId))
|
||||
}, [getNodeAvailableVars, isChatMode, parentAvailableNodes])
|
||||
|
||||
const mentionConfig = useMemo<MentionConfig>(() => {
|
||||
const current = toolParam?.mention_config
|
||||
const nestedNodeConfig = useMemo<NestedNodeConfig>(() => {
|
||||
const current = toolParam?.nested_node_config
|
||||
const rawSelector = Array.isArray(current?.output_selector) ? current!.output_selector : []
|
||||
const outputSelector = rawSelector[0] === extractorNodeId ? rawSelector.slice(1) : rawSelector
|
||||
const defaultOutputSelector = ['structured_output', paramKey]
|
||||
@ -100,9 +100,9 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
null_strategy: current?.null_strategy || 'use_default',
|
||||
default_value: current?.default_value ?? '',
|
||||
}
|
||||
}, [extractorNodeId, paramKey, toolParam?.mention_config])
|
||||
}, [extractorNodeId, paramKey, toolParam?.nested_node_config])
|
||||
|
||||
const handleMentionConfigChange = useCallback((config: MentionConfig) => {
|
||||
const handleNestedNodeConfigChange = useCallback((config: NestedNodeConfig) => {
|
||||
if (!isAgentVariant)
|
||||
return
|
||||
|
||||
@ -124,8 +124,8 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
...toolData.tool_parameters,
|
||||
[paramKey]: {
|
||||
...currentParam,
|
||||
type: currentParam.type || VarKindType.mention,
|
||||
mention_config: config,
|
||||
type: currentParam.type || VarKindType.nested_node,
|
||||
nested_node_config: config,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -136,18 +136,18 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
}, [handleSyncWorkflowDraft, isAgentVariant, paramKey, reactflowStore, toolNodeId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAgentVariant || !toolParam || (toolParam.type && toolParam.type !== VarKindType.mention))
|
||||
if (!isAgentVariant || !toolParam || (toolParam.type && toolParam.type !== VarKindType.nested_node))
|
||||
return
|
||||
|
||||
const current = toolParam.mention_config
|
||||
const current = toolParam.nested_node_config
|
||||
const needsExtractor = !current?.extractor_node_id
|
||||
const needsNullStrategy = !current?.null_strategy
|
||||
const needsOutputSelector = !Array.isArray(current?.output_selector)
|
||||
const needsDefaultValue = current?.default_value === undefined
|
||||
|
||||
if (needsExtractor || needsNullStrategy || needsOutputSelector || needsDefaultValue)
|
||||
handleMentionConfigChange(mentionConfig)
|
||||
}, [handleMentionConfigChange, isAgentVariant, mentionConfig, toolParam])
|
||||
handleNestedNodeConfigChange(nestedNodeConfig)
|
||||
}, [handleNestedNodeConfigChange, isAgentVariant, nestedNodeConfig, toolParam])
|
||||
|
||||
const getUserPromptText = useCallback((promptTemplate?: PromptTemplateItem[] | PromptItem) => {
|
||||
if (!promptTemplate)
|
||||
@ -281,8 +281,8 @@ const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
agentNodeId={props.agentNodeId}
|
||||
agentName={props.agentName}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={handleMentionConfigChange}
|
||||
nestedNodeConfig={nestedNodeConfig}
|
||||
onNestedNodeConfigChange={handleNestedNodeConfigChange}
|
||||
extractorNode={extractorNode as Node<LLMNodeType> | undefined}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentAvailableNodes}
|
||||
|
||||
@ -77,7 +77,7 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
}, {} as Record<string, WorkflowNode>)
|
||||
}, [nodes])
|
||||
|
||||
const mentionEntries = useMemo(() => {
|
||||
const nestedNodeEntries = useMemo(() => {
|
||||
const entries: Array<{ agentNodeId: string, extractorNodeId?: string, paramKey: string }> = []
|
||||
const seen = new Set<string>()
|
||||
const toolParams = data.tool_parameters || {}
|
||||
@ -97,8 +97,8 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
entries.push({
|
||||
agentNodeId,
|
||||
paramKey,
|
||||
extractorNodeId: param?.mention_config?.extractor_node_id
|
||||
|| (param?.type === VarType.mention ? `${id}_ext_${paramKey}` : undefined),
|
||||
extractorNodeId: param?.nested_node_config?.extractor_node_id
|
||||
|| (param?.type === VarType.nested_node ? `${id}_ext_${paramKey}` : undefined),
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -106,7 +106,7 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
}, [data.tool_parameters, id])
|
||||
|
||||
const referenceItems = useMemo(() => {
|
||||
if (!mentionEntries.length)
|
||||
if (!nestedNodeEntries.length)
|
||||
return []
|
||||
|
||||
const getNodeWarning = (node?: WorkflowNode) => {
|
||||
@ -132,7 +132,7 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
return Boolean(errorMessage)
|
||||
}
|
||||
|
||||
return mentionEntries.map(({ agentNodeId, extractorNodeId, paramKey }) => {
|
||||
return nestedNodeEntries.map(({ agentNodeId, extractorNodeId, paramKey }) => {
|
||||
const agentNode = nodesById[agentNodeId]
|
||||
const agentLabel = `@${agentNode?.data.title || agentNodeId}`
|
||||
const agentWarning = getNodeWarning(agentNode)
|
||||
@ -148,7 +148,7 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
hasWarning,
|
||||
}
|
||||
})
|
||||
}, [mentionEntries, nodesById, nodesMetaDataMap, strategyProviders, language, t])
|
||||
}, [nestedNodeEntries, nodesById, nodesMetaDataMap, strategyProviders, language, t])
|
||||
|
||||
const hasConfigs = toolConfigs.length > 0
|
||||
const hasReferences = referenceItems.length > 0
|
||||
|
||||
@ -32,7 +32,7 @@ const useSingleRunFormParams = ({
|
||||
const { inputs } = useNodeCrud<ToolNodeType>(id, payload)
|
||||
|
||||
const hadVarParams = Object.keys(inputs.tool_parameters)
|
||||
.filter(key => ![VarType.constant, VarType.mention].includes(inputs.tool_parameters[key].type))
|
||||
.filter(key => ![VarType.constant, VarType.nested_node].includes(inputs.tool_parameters[key].type))
|
||||
.map(k => inputs.tool_parameters[k])
|
||||
|
||||
const hadVarSettings = Object.keys(inputs.tool_configurations)
|
||||
|
||||
@ -4,8 +4,8 @@ import type { FlowType } from '@/types/common'
|
||||
import type {
|
||||
ConversationVariableResponse,
|
||||
FetchWorkflowDraftResponse,
|
||||
MentionGraphPayload,
|
||||
MentionGraphResponse,
|
||||
NestedNodeGraphPayload,
|
||||
NestedNodeGraphResponse,
|
||||
NodesDefaultConfigsResponse,
|
||||
VarInInspect,
|
||||
} from '@/types/workflow'
|
||||
@ -34,8 +34,8 @@ export const fetchNodesDefaultConfigs = (url: string) => {
|
||||
return get<NodesDefaultConfigsResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchMentionGraph = (flowType: FlowType, flowId: string, payload: MentionGraphPayload) => {
|
||||
return post<MentionGraphResponse>(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/mention-graph`, { body: payload }, { silent: true })
|
||||
export const fetchNestedNodeGraph = (flowType: FlowType, flowId: string, payload: NestedNodeGraphPayload) => {
|
||||
return post<NestedNodeGraphResponse>(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nested-node-graph`, { body: payload }, { silent: true })
|
||||
}
|
||||
|
||||
export const singleNodeRun = (flowType: FlowType, flowId: string, nodeId: string, params: object) => {
|
||||
|
||||
@ -200,20 +200,20 @@ export type FetchWorkflowDraftResponse = {
|
||||
marked_comment: string
|
||||
}
|
||||
|
||||
export type MentionParameterSchema = {
|
||||
export type NestedNodeParameterSchema = {
|
||||
name: string
|
||||
type: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type MentionGraphPayload = {
|
||||
export type NestedNodeGraphPayload = {
|
||||
parent_node_id: string
|
||||
parameter_key: string
|
||||
context_source: ValueSelector
|
||||
parameter_schema: MentionParameterSchema
|
||||
parameter_schema: NestedNodeParameterSchema
|
||||
}
|
||||
|
||||
export type MentionGraphResponse = {
|
||||
export type NestedNodeGraphResponse = {
|
||||
graph: {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
|
||||
Reference in New Issue
Block a user