mirror of
https://github.com/langgenius/dify.git
synced 2026-05-30 16:00:32 -04:00
feat: extract model runtime
Signed-off-by: -LAN- <laipz8200@outlook.com>
This commit is contained in:
@@ -9,8 +9,8 @@ from sqlalchemy.orm import Session
|
||||
from typing_extensions import override
|
||||
|
||||
from configs import dify_config
|
||||
from core.app.entities.app_invoke_entities import DifyRunContext
|
||||
from core.app.llm.model_access import build_dify_model_access
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
|
||||
from core.app.llm.model_access import build_dify_model_access, fetch_model_config
|
||||
from core.helper.code_executor.code_executor import (
|
||||
CodeExecutionError,
|
||||
CodeExecutor,
|
||||
@@ -20,8 +20,17 @@ from core.memory.token_buffer_memory import TokenBufferMemory
|
||||
from core.model_manager import ModelInstance
|
||||
from core.prompt.entities.advanced_prompt_entities import MemoryConfig
|
||||
from core.repositories.human_input_repository import HumanInputFormRepositoryImpl
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
from core.trigger.constants import TRIGGER_NODE_TYPES
|
||||
from core.workflow.node_runtime import (
|
||||
DifyFileReferenceFactory,
|
||||
DifyHumanInputNodeRuntime,
|
||||
DifyPreparedLLM,
|
||||
DifyPromptMessageSerializer,
|
||||
DifyRetrieverAttachmentLoader,
|
||||
DifyToolFileManager,
|
||||
DifyToolNodeRuntime,
|
||||
build_dify_llm_file_saver,
|
||||
)
|
||||
from core.workflow.nodes.agent.message_transformer import AgentMessageTransformer
|
||||
from core.workflow.nodes.agent.plugin_strategy_adapter import (
|
||||
PluginAgentStrategyPresentationProvider,
|
||||
@@ -30,11 +39,9 @@ from core.workflow.nodes.agent.plugin_strategy_adapter import (
|
||||
from core.workflow.nodes.agent.runtime_support import AgentRuntimeSupport
|
||||
from dify_graph.entities.base_node_data import BaseNodeData
|
||||
from dify_graph.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
|
||||
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY
|
||||
from dify_graph.enums import BuiltinNodeTypes, NodeType, SystemVariableKey
|
||||
from dify_graph.file.file_manager import file_manager
|
||||
from dify_graph.graph.graph import NodeFactory
|
||||
from dify_graph.model_runtime.entities.model_entities import ModelType
|
||||
from dify_graph.model_runtime.memory import PromptMessageMemory
|
||||
from dify_graph.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
|
||||
from dify_graph.nodes.base.node import Node
|
||||
@@ -44,13 +51,10 @@ from dify_graph.nodes.code.limits import CodeNodeLimits
|
||||
from dify_graph.nodes.document_extractor import UnstructuredApiConfig
|
||||
from dify_graph.nodes.http_request import build_http_request_config
|
||||
from dify_graph.nodes.llm.entities import LLMNodeData
|
||||
from dify_graph.nodes.llm.exc import LLMModeRequiredError, ModelNotExistError
|
||||
from dify_graph.nodes.llm.protocols import TemplateRenderer
|
||||
from dify_graph.nodes.parameter_extractor.entities import ParameterExtractorNodeData
|
||||
from dify_graph.nodes.question_classifier.entities import QuestionClassifierNodeData
|
||||
from dify_graph.nodes.template_transform.template_renderer import (
|
||||
CodeExecutorJinja2TemplateRenderer,
|
||||
)
|
||||
from dify_graph.template_rendering import CodeExecutorJinja2TemplateRenderer
|
||||
from dify_graph.variables.segments import StringSegment
|
||||
from extensions.ext_database import db
|
||||
from models.model import Conversation
|
||||
@@ -264,11 +268,22 @@ class DifyNodeFactory(NodeFactory):
|
||||
max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
|
||||
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
|
||||
)
|
||||
self._template_renderer = CodeExecutorJinja2TemplateRenderer(code_executor=self._code_executor)
|
||||
self._jinja2_template_renderer = CodeExecutorJinja2TemplateRenderer(code_executor=self._code_executor)
|
||||
self._llm_template_renderer: TemplateRenderer = DefaultLLMTemplateRenderer()
|
||||
self._template_transform_max_output_length = dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
|
||||
self._http_request_http_client = ssrf_proxy
|
||||
self._http_request_tool_file_manager_factory = ToolFileManager
|
||||
self._bound_tool_file_manager_factory = lambda: DifyToolFileManager(self._dify_context)
|
||||
self._file_reference_factory = DifyFileReferenceFactory(self._dify_context)
|
||||
self._prompt_message_serializer = DifyPromptMessageSerializer()
|
||||
self._retriever_attachment_loader = DifyRetrieverAttachmentLoader(
|
||||
file_reference_factory=self._file_reference_factory,
|
||||
)
|
||||
self._llm_file_saver = build_dify_llm_file_saver(
|
||||
run_context=self._dify_context,
|
||||
http_client=self._http_request_http_client,
|
||||
)
|
||||
self._human_input_runtime = DifyHumanInputNodeRuntime(self._dify_context)
|
||||
self._tool_runtime = DifyToolNodeRuntime(self._dify_context)
|
||||
self._http_request_file_manager = file_manager
|
||||
self._document_extractor_unstructured_api_config = UnstructuredApiConfig(
|
||||
api_url=dify_config.UNSTRUCTURED_API_URL,
|
||||
@@ -284,7 +299,7 @@ class DifyNodeFactory(NodeFactory):
|
||||
ssrf_default_max_retries=dify_config.SSRF_DEFAULT_MAX_RETRIES,
|
||||
)
|
||||
|
||||
self._llm_credentials_provider, self._llm_model_factory = build_dify_model_access(self._dify_context.tenant_id)
|
||||
self._llm_credentials_provider, self._llm_model_factory = build_dify_model_access(self._dify_context)
|
||||
self._agent_strategy_resolver = PluginAgentStrategyResolver()
|
||||
self._agent_strategy_presentation_provider = PluginAgentStrategyPresentationProvider()
|
||||
self._agent_runtime_support = AgentRuntimeSupport()
|
||||
@@ -321,22 +336,32 @@ class DifyNodeFactory(NodeFactory):
|
||||
"code_limits": self._code_limits,
|
||||
},
|
||||
BuiltinNodeTypes.TEMPLATE_TRANSFORM: lambda: {
|
||||
"template_renderer": self._template_renderer,
|
||||
"jinja2_template_renderer": self._jinja2_template_renderer,
|
||||
"max_output_length": self._template_transform_max_output_length,
|
||||
},
|
||||
BuiltinNodeTypes.HTTP_REQUEST: lambda: {
|
||||
"http_request_config": self._http_request_config,
|
||||
"http_client": self._http_request_http_client,
|
||||
"tool_file_manager_factory": self._http_request_tool_file_manager_factory,
|
||||
"tool_file_manager_factory": self._bound_tool_file_manager_factory,
|
||||
"file_manager": self._http_request_file_manager,
|
||||
"file_reference_factory": self._file_reference_factory,
|
||||
},
|
||||
BuiltinNodeTypes.HUMAN_INPUT: lambda: {
|
||||
"form_repository": HumanInputFormRepositoryImpl(tenant_id=self._dify_context.tenant_id),
|
||||
"form_repository": HumanInputFormRepositoryImpl(
|
||||
tenant_id=self._dify_context.tenant_id,
|
||||
app_id=self._dify_context.app_id,
|
||||
),
|
||||
"runtime": self._human_input_runtime,
|
||||
},
|
||||
BuiltinNodeTypes.LLM: lambda: self._build_llm_compatible_node_init_kwargs(
|
||||
node_class=node_class,
|
||||
node_data=node_data,
|
||||
wrap_model_instance=True,
|
||||
include_http_client=True,
|
||||
include_llm_file_saver=True,
|
||||
include_prompt_message_serializer=True,
|
||||
include_retriever_attachment_loader=True,
|
||||
include_jinja2_template_renderer=True,
|
||||
),
|
||||
BuiltinNodeTypes.DOCUMENT_EXTRACTOR: lambda: {
|
||||
"unstructured_api_config": self._document_extractor_unstructured_api_config,
|
||||
@@ -345,15 +370,26 @@ class DifyNodeFactory(NodeFactory):
|
||||
BuiltinNodeTypes.QUESTION_CLASSIFIER: lambda: self._build_llm_compatible_node_init_kwargs(
|
||||
node_class=node_class,
|
||||
node_data=node_data,
|
||||
wrap_model_instance=True,
|
||||
include_http_client=True,
|
||||
include_llm_file_saver=True,
|
||||
include_prompt_message_serializer=True,
|
||||
include_retriever_attachment_loader=False,
|
||||
include_jinja2_template_renderer=False,
|
||||
),
|
||||
BuiltinNodeTypes.PARAMETER_EXTRACTOR: lambda: self._build_llm_compatible_node_init_kwargs(
|
||||
node_class=node_class,
|
||||
node_data=node_data,
|
||||
wrap_model_instance=True,
|
||||
include_http_client=False,
|
||||
include_llm_file_saver=False,
|
||||
include_prompt_message_serializer=True,
|
||||
include_retriever_attachment_loader=False,
|
||||
include_jinja2_template_renderer=False,
|
||||
),
|
||||
BuiltinNodeTypes.TOOL: lambda: {
|
||||
"tool_file_manager_factory": self._http_request_tool_file_manager_factory(),
|
||||
"tool_file_manager_factory": self._bound_tool_file_manager_factory(),
|
||||
"runtime": self._tool_runtime,
|
||||
},
|
||||
BuiltinNodeTypes.AGENT: lambda: {
|
||||
"strategy_resolver": self._agent_strategy_resolver,
|
||||
@@ -387,7 +423,12 @@ class DifyNodeFactory(NodeFactory):
|
||||
*,
|
||||
node_class: type[Node],
|
||||
node_data: BaseNodeData,
|
||||
wrap_model_instance: bool,
|
||||
include_http_client: bool,
|
||||
include_llm_file_saver: bool,
|
||||
include_prompt_message_serializer: bool,
|
||||
include_retriever_attachment_loader: bool,
|
||||
include_jinja2_template_renderer: bool,
|
||||
) -> dict[str, object]:
|
||||
validated_node_data = cast(
|
||||
LLMCompatibleNodeData,
|
||||
@@ -397,49 +438,33 @@ class DifyNodeFactory(NodeFactory):
|
||||
node_init_kwargs: dict[str, object] = {
|
||||
"credentials_provider": self._llm_credentials_provider,
|
||||
"model_factory": self._llm_model_factory,
|
||||
"model_instance": model_instance,
|
||||
"model_instance": DifyPreparedLLM(model_instance) if wrap_model_instance else model_instance,
|
||||
"memory": self._build_memory_for_llm_node(
|
||||
node_data=validated_node_data,
|
||||
model_instance=model_instance,
|
||||
),
|
||||
}
|
||||
if validated_node_data.type in {BuiltinNodeTypes.LLM, BuiltinNodeTypes.QUESTION_CLASSIFIER}:
|
||||
if validated_node_data.type == BuiltinNodeTypes.QUESTION_CLASSIFIER:
|
||||
node_init_kwargs["template_renderer"] = self._llm_template_renderer
|
||||
if include_http_client:
|
||||
node_init_kwargs["http_client"] = self._http_request_http_client
|
||||
if include_llm_file_saver:
|
||||
node_init_kwargs["llm_file_saver"] = self._llm_file_saver
|
||||
if include_prompt_message_serializer:
|
||||
node_init_kwargs["prompt_message_serializer"] = self._prompt_message_serializer
|
||||
if include_retriever_attachment_loader:
|
||||
node_init_kwargs["retriever_attachment_loader"] = self._retriever_attachment_loader
|
||||
if include_jinja2_template_renderer:
|
||||
node_init_kwargs["jinja2_template_renderer"] = self._jinja2_template_renderer
|
||||
return node_init_kwargs
|
||||
|
||||
def _build_model_instance_for_llm_node(self, node_data: LLMCompatibleNodeData) -> ModelInstance:
|
||||
node_data_model = node_data.model
|
||||
if not node_data_model.mode:
|
||||
raise LLMModeRequiredError("LLM mode is required.")
|
||||
|
||||
credentials = self._llm_credentials_provider.fetch(node_data_model.provider, node_data_model.name)
|
||||
model_instance = self._llm_model_factory.init_model_instance(node_data_model.provider, node_data_model.name)
|
||||
provider_model_bundle = model_instance.provider_model_bundle
|
||||
|
||||
provider_model = provider_model_bundle.configuration.get_provider_model(
|
||||
model=node_data_model.name,
|
||||
model_type=ModelType.LLM,
|
||||
model_instance, _ = fetch_model_config(
|
||||
node_data_model=node_data_model,
|
||||
credentials_provider=self._llm_credentials_provider,
|
||||
model_factory=self._llm_model_factory,
|
||||
)
|
||||
if provider_model is None:
|
||||
raise ModelNotExistError(f"Model {node_data_model.name} not exist.")
|
||||
provider_model.raise_for_status()
|
||||
|
||||
completion_params = dict(node_data_model.completion_params)
|
||||
stop = completion_params.pop("stop", [])
|
||||
if not isinstance(stop, list):
|
||||
stop = []
|
||||
|
||||
model_schema = model_instance.model_type_instance.get_model_schema(node_data_model.name, credentials)
|
||||
if not model_schema:
|
||||
raise ModelNotExistError(f"Model {node_data_model.name} not exist.")
|
||||
|
||||
model_instance.provider = node_data_model.provider
|
||||
model_instance.model_name = node_data_model.name
|
||||
model_instance.credentials = credentials
|
||||
model_instance.parameters = completion_params
|
||||
model_instance.stop = tuple(stop)
|
||||
model_instance.model_type_instance = cast(LargeLanguageModel, model_instance.model_type_instance)
|
||||
return model_instance
|
||||
|
||||
|
||||
532
api/core/workflow/node_runtime.py
Normal file
532
api/core/workflow/node_runtime.py
Normal file
@@ -0,0 +1,532 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
|
||||
from core.callback_handler.workflow_tool_callback_handler import DifyWorkflowCallbackHandler
|
||||
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.model_manager import ModelInstance
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError, PluginInvokeError
|
||||
from core.plugin.impl.plugin import PluginInstaller
|
||||
from core.prompt.utils.prompt_message_util import PromptMessageUtil
|
||||
from core.tools.entities.tool_entities import ToolProviderType as CoreToolProviderType
|
||||
from core.tools.errors import ToolInvokeError
|
||||
from core.tools.signature import sign_upload_file
|
||||
from core.tools.tool_engine import ToolEngine
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.tools.utils.message_transformer import ToolFileMessageTransformer
|
||||
from dify_graph.file import FileTransferMethod, FileType
|
||||
from dify_graph.model_runtime.entities import LLMMode
|
||||
from dify_graph.model_runtime.entities.llm_entities import (
|
||||
LLMResult,
|
||||
LLMResultChunk,
|
||||
LLMResultChunkWithStructuredOutput,
|
||||
LLMResultWithStructuredOutput,
|
||||
LLMUsage,
|
||||
)
|
||||
from dify_graph.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool
|
||||
from dify_graph.model_runtime.entities.model_entities import AIModelEntity
|
||||
from dify_graph.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
|
||||
from dify_graph.nodes.human_input.entities import DeliveryChannelConfig, apply_debug_email_recipient
|
||||
from dify_graph.nodes.llm.runtime_protocols import (
|
||||
PreparedLLMProtocol,
|
||||
PromptMessageSerializerProtocol,
|
||||
RetrieverAttachmentLoaderProtocol,
|
||||
)
|
||||
from dify_graph.nodes.protocols import FileReferenceFactoryProtocol, HttpClientProtocol, ToolFileManagerProtocol
|
||||
from dify_graph.nodes.runtime import (
|
||||
HumanInputNodeRuntimeProtocol,
|
||||
ToolNodeRuntimeProtocol,
|
||||
)
|
||||
from dify_graph.nodes.tool.exc import ToolNodeError, ToolRuntimeInvocationError, ToolRuntimeResolutionError
|
||||
from dify_graph.nodes.tool_runtime_entities import (
|
||||
ToolRuntimeHandle,
|
||||
ToolRuntimeMessage,
|
||||
ToolRuntimeParameter,
|
||||
)
|
||||
from extensions.ext_database import db
|
||||
from factories import file_factory
|
||||
from models.dataset import SegmentAttachmentBinding
|
||||
from models.model import UploadFile
|
||||
from services.tools.builtin_tools_manage_service import BuiltinToolManageService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.tools.__base.tool import Tool
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage as CoreToolInvokeMessage
|
||||
from dify_graph.file import File
|
||||
from dify_graph.nodes.llm.file_saver import LLMFileSaver
|
||||
from dify_graph.nodes.tool.entities import ToolNodeData
|
||||
|
||||
|
||||
def resolve_dify_run_context(run_context: Mapping[str, Any] | DifyRunContext) -> DifyRunContext:
|
||||
if isinstance(run_context, DifyRunContext):
|
||||
return run_context
|
||||
|
||||
raw_ctx = run_context.get(DIFY_RUN_CONTEXT_KEY)
|
||||
if raw_ctx is None:
|
||||
raise ValueError(f"run_context missing required key: {DIFY_RUN_CONTEXT_KEY}")
|
||||
if isinstance(raw_ctx, DifyRunContext):
|
||||
return raw_ctx
|
||||
return DifyRunContext.model_validate(raw_ctx)
|
||||
|
||||
|
||||
class DifyFileReferenceFactory(FileReferenceFactoryProtocol):
|
||||
def __init__(self, run_context: Mapping[str, Any] | DifyRunContext) -> None:
|
||||
self._run_context = resolve_dify_run_context(run_context)
|
||||
|
||||
def build_from_mapping(self, *, mapping: Mapping[str, Any]):
|
||||
return file_factory.build_from_mapping(
|
||||
mapping=mapping,
|
||||
tenant_id=self._run_context.tenant_id,
|
||||
)
|
||||
|
||||
|
||||
class DifyPreparedLLM(PreparedLLMProtocol):
|
||||
"""Workflow-layer adapter that hides the full `ModelInstance` API from `dify_graph` nodes."""
|
||||
|
||||
def __init__(self, model_instance: ModelInstance) -> None:
|
||||
self._model_instance = model_instance
|
||||
|
||||
@property
|
||||
def provider(self) -> str:
|
||||
return self._model_instance.provider
|
||||
|
||||
@property
|
||||
def model_name(self) -> str:
|
||||
return self._model_instance.model_name
|
||||
|
||||
@property
|
||||
def parameters(self) -> Mapping[str, Any]:
|
||||
return self._model_instance.parameters
|
||||
|
||||
@property
|
||||
def stop(self) -> Sequence[str] | None:
|
||||
return self._model_instance.stop
|
||||
|
||||
def get_model_schema(self) -> AIModelEntity:
|
||||
model_schema = cast(LargeLanguageModel, self._model_instance.model_type_instance).get_model_schema(
|
||||
self._model_instance.model_name,
|
||||
self._model_instance.credentials,
|
||||
)
|
||||
if model_schema is None:
|
||||
raise ValueError(f"Model schema not found for {self._model_instance.model_name}")
|
||||
return model_schema
|
||||
|
||||
def get_llm_num_tokens(self, prompt_messages: Sequence[PromptMessage]) -> int:
|
||||
return self._model_instance.get_llm_num_tokens(prompt_messages)
|
||||
|
||||
def invoke_llm(
|
||||
self,
|
||||
*,
|
||||
prompt_messages: Sequence[PromptMessage],
|
||||
model_parameters: Mapping[str, Any],
|
||||
tools: Sequence[PromptMessageTool] | None,
|
||||
stop: Sequence[str] | None,
|
||||
stream: bool,
|
||||
) -> LLMResult | Generator[LLMResultChunk, None, None]:
|
||||
return self._model_instance.invoke_llm(
|
||||
prompt_messages=list(prompt_messages),
|
||||
model_parameters=dict(model_parameters),
|
||||
tools=list(tools or []),
|
||||
stop=list(stop or []),
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
def invoke_llm_with_structured_output(
|
||||
self,
|
||||
*,
|
||||
prompt_messages: Sequence[PromptMessage],
|
||||
json_schema: Mapping[str, Any],
|
||||
model_parameters: Mapping[str, Any],
|
||||
stop: Sequence[str] | None,
|
||||
stream: bool,
|
||||
) -> LLMResultWithStructuredOutput | Generator[LLMResultChunkWithStructuredOutput, None, None]:
|
||||
return invoke_llm_with_structured_output(
|
||||
provider=self.provider,
|
||||
model_schema=self.get_model_schema(),
|
||||
model_instance=self._model_instance,
|
||||
prompt_messages=prompt_messages,
|
||||
json_schema=json_schema,
|
||||
model_parameters=model_parameters,
|
||||
stop=list(stop or []),
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
def is_structured_output_parse_error(self, error: Exception) -> bool:
|
||||
return isinstance(error, OutputParserError)
|
||||
|
||||
|
||||
class DifyPromptMessageSerializer(PromptMessageSerializerProtocol):
|
||||
def serialize(
|
||||
self,
|
||||
*,
|
||||
model_mode: LLMMode,
|
||||
prompt_messages: Sequence[PromptMessage],
|
||||
) -> Any:
|
||||
return PromptMessageUtil.prompt_messages_to_prompt_for_saving(
|
||||
model_mode=model_mode,
|
||||
prompt_messages=prompt_messages,
|
||||
)
|
||||
|
||||
|
||||
class DifyRetrieverAttachmentLoader(RetrieverAttachmentLoaderProtocol):
|
||||
"""Resolve retriever attachments through Dify persistence and return graph file references."""
|
||||
|
||||
def __init__(self, *, file_reference_factory: FileReferenceFactoryProtocol) -> None:
|
||||
self._file_reference_factory = file_reference_factory
|
||||
|
||||
def load(self, *, segment_id: str) -> Sequence[File]:
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
attachments_with_bindings = session.execute(
|
||||
select(SegmentAttachmentBinding, UploadFile)
|
||||
.join(UploadFile, UploadFile.id == SegmentAttachmentBinding.attachment_id)
|
||||
.where(SegmentAttachmentBinding.segment_id == segment_id)
|
||||
).all()
|
||||
|
||||
return [
|
||||
self._file_reference_factory.build_from_mapping(
|
||||
mapping={
|
||||
"id": upload_file.id,
|
||||
"filename": upload_file.name,
|
||||
"extension": "." + upload_file.extension,
|
||||
"mime_type": upload_file.mime_type,
|
||||
"type": FileType.IMAGE,
|
||||
"transfer_method": FileTransferMethod.LOCAL_FILE,
|
||||
"remote_url": upload_file.source_url,
|
||||
"related_id": upload_file.id,
|
||||
"upload_file_id": upload_file.id,
|
||||
"size": upload_file.size,
|
||||
"storage_key": upload_file.key,
|
||||
"url": sign_upload_file(upload_file.id, upload_file.extension),
|
||||
}
|
||||
)
|
||||
for _, upload_file in attachments_with_bindings
|
||||
]
|
||||
|
||||
|
||||
class DifyToolFileManager(ToolFileManagerProtocol):
|
||||
def __init__(self, run_context: Mapping[str, Any] | DifyRunContext) -> None:
|
||||
self._run_context = resolve_dify_run_context(run_context)
|
||||
self._manager = ToolFileManager()
|
||||
|
||||
def create_file_by_raw(
|
||||
self,
|
||||
*,
|
||||
conversation_id: str | None,
|
||||
file_binary: bytes,
|
||||
mimetype: str,
|
||||
filename: str | None = None,
|
||||
) -> Any:
|
||||
return self._manager.create_file_by_raw(
|
||||
user_id=self._run_context.user_id,
|
||||
tenant_id=self._run_context.tenant_id,
|
||||
conversation_id=conversation_id,
|
||||
file_binary=file_binary,
|
||||
mimetype=mimetype,
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
def get_file_generator_by_tool_file_id(self, tool_file_id: str):
|
||||
return self._manager.get_file_generator_by_tool_file_id(tool_file_id)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _WorkflowToolRuntimeSpec:
|
||||
provider_type: CoreToolProviderType
|
||||
provider_id: str
|
||||
tool_name: str
|
||||
tool_configurations: dict[str, Any]
|
||||
credential_id: str | None = None
|
||||
|
||||
|
||||
class DifyToolNodeRuntime(ToolNodeRuntimeProtocol):
|
||||
def __init__(self, run_context: Mapping[str, Any] | DifyRunContext) -> None:
|
||||
self._run_context = resolve_dify_run_context(run_context)
|
||||
self._file_reference_factory = DifyFileReferenceFactory(self._run_context)
|
||||
|
||||
@property
|
||||
def file_reference_factory(self) -> FileReferenceFactoryProtocol:
|
||||
return self._file_reference_factory
|
||||
|
||||
def build_file_reference(self, *, mapping: Mapping[str, Any]):
|
||||
return self._file_reference_factory.build_from_mapping(mapping=mapping)
|
||||
|
||||
def get_runtime(
|
||||
self,
|
||||
*,
|
||||
node_id: str,
|
||||
node_data: ToolNodeData,
|
||||
variable_pool,
|
||||
) -> ToolRuntimeHandle:
|
||||
try:
|
||||
tool_runtime = ToolManager.get_workflow_tool_runtime(
|
||||
self._run_context.tenant_id,
|
||||
self._run_context.app_id,
|
||||
node_id,
|
||||
self._build_tool_runtime_spec(node_data),
|
||||
self._run_context.invoke_from,
|
||||
variable_pool,
|
||||
)
|
||||
except ToolNodeError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise ToolRuntimeResolutionError(str(exc)) from exc
|
||||
|
||||
return ToolRuntimeHandle(raw=tool_runtime)
|
||||
|
||||
def get_runtime_parameters(
|
||||
self,
|
||||
*,
|
||||
tool_runtime: ToolRuntimeHandle,
|
||||
) -> Sequence[ToolRuntimeParameter]:
|
||||
tool = self._tool_from_handle(tool_runtime)
|
||||
return [
|
||||
ToolRuntimeParameter(name=parameter.name, required=parameter.required)
|
||||
for parameter in (tool.get_merged_runtime_parameters() or [])
|
||||
]
|
||||
|
||||
def invoke(
|
||||
self,
|
||||
*,
|
||||
tool_runtime: ToolRuntimeHandle,
|
||||
tool_parameters: Mapping[str, Any],
|
||||
workflow_call_depth: int,
|
||||
conversation_id: str | None,
|
||||
provider_name: str,
|
||||
) -> Generator[ToolRuntimeMessage, None, None]:
|
||||
tool = self._tool_from_handle(tool_runtime)
|
||||
callback = DifyWorkflowCallbackHandler()
|
||||
|
||||
try:
|
||||
messages = ToolEngine.generic_invoke(
|
||||
tool=tool,
|
||||
tool_parameters=dict(tool_parameters),
|
||||
user_id=self._run_context.user_id,
|
||||
workflow_tool_callback=callback,
|
||||
workflow_call_depth=workflow_call_depth,
|
||||
app_id=self._run_context.app_id,
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise self._map_invocation_exception(exc, provider_name=provider_name) from exc
|
||||
|
||||
transformed_messages = ToolFileMessageTransformer.transform_tool_invoke_messages(
|
||||
messages=messages,
|
||||
user_id=self._run_context.user_id,
|
||||
tenant_id=self._run_context.tenant_id,
|
||||
conversation_id=None,
|
||||
)
|
||||
|
||||
return self._adapt_messages(transformed_messages, provider_name=provider_name)
|
||||
|
||||
def get_usage(
|
||||
self,
|
||||
*,
|
||||
tool_runtime: ToolRuntimeHandle,
|
||||
) -> LLMUsage:
|
||||
latest = getattr(tool_runtime.raw, "latest_usage", None)
|
||||
if isinstance(latest, LLMUsage):
|
||||
return latest
|
||||
if isinstance(latest, dict):
|
||||
return LLMUsage.model_validate(latest)
|
||||
return LLMUsage.empty_usage()
|
||||
|
||||
def resolve_provider_icons(
|
||||
self,
|
||||
*,
|
||||
provider_name: str,
|
||||
default_icon: str | None = None,
|
||||
) -> tuple[str | None, str | None]:
|
||||
icon = default_icon
|
||||
icon_dark = None
|
||||
|
||||
manager = PluginInstaller()
|
||||
plugins = manager.list_plugins(self._run_context.tenant_id)
|
||||
try:
|
||||
current_plugin = next(plugin for plugin in plugins if f"{plugin.plugin_id}/{plugin.name}" == provider_name)
|
||||
icon = current_plugin.declaration.icon
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
try:
|
||||
builtin_tool = next(
|
||||
provider
|
||||
for provider in BuiltinToolManageService.list_builtin_tools(
|
||||
self._run_context.user_id,
|
||||
self._run_context.tenant_id,
|
||||
)
|
||||
if provider.name == provider_name
|
||||
)
|
||||
icon = builtin_tool.icon
|
||||
icon_dark = builtin_tool.icon_dark
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
return icon, icon_dark
|
||||
|
||||
@staticmethod
|
||||
def _tool_from_handle(tool_runtime: ToolRuntimeHandle) -> Tool:
|
||||
return cast("Tool", tool_runtime.raw)
|
||||
|
||||
@staticmethod
|
||||
def _build_tool_runtime_spec(node_data: ToolNodeData) -> _WorkflowToolRuntimeSpec:
|
||||
return _WorkflowToolRuntimeSpec(
|
||||
provider_type=CoreToolProviderType(node_data.provider_type.value),
|
||||
provider_id=node_data.provider_id,
|
||||
tool_name=node_data.tool_name,
|
||||
tool_configurations=dict(node_data.tool_configurations),
|
||||
credential_id=node_data.credential_id,
|
||||
)
|
||||
|
||||
def _adapt_messages(
|
||||
self,
|
||||
messages: Generator[CoreToolInvokeMessage, None, None],
|
||||
*,
|
||||
provider_name: str,
|
||||
) -> Generator[ToolRuntimeMessage, None, None]:
|
||||
try:
|
||||
for message in messages:
|
||||
yield self._convert_message(message)
|
||||
except Exception as exc:
|
||||
raise self._map_invocation_exception(exc, provider_name=provider_name) from exc
|
||||
|
||||
def _convert_message(self, message: CoreToolInvokeMessage) -> ToolRuntimeMessage:
|
||||
graph_message_type = ToolRuntimeMessage.MessageType(message.type.value)
|
||||
graph_message = self._convert_message_payload(message.message)
|
||||
graph_meta = message.meta.copy() if message.meta is not None else None
|
||||
return ToolRuntimeMessage(type=graph_message_type, message=graph_message, meta=graph_meta)
|
||||
|
||||
def _convert_message_payload(
|
||||
self,
|
||||
message: CoreToolInvokeMessage.TextMessage
|
||||
| CoreToolInvokeMessage.JsonMessage
|
||||
| CoreToolInvokeMessage.BlobChunkMessage
|
||||
| CoreToolInvokeMessage.BlobMessage
|
||||
| CoreToolInvokeMessage.LogMessage
|
||||
| CoreToolInvokeMessage.FileMessage
|
||||
| CoreToolInvokeMessage.VariableMessage
|
||||
| CoreToolInvokeMessage.RetrieverResourceMessage
|
||||
| None,
|
||||
) -> (
|
||||
ToolRuntimeMessage.TextMessage
|
||||
| ToolRuntimeMessage.JsonMessage
|
||||
| ToolRuntimeMessage.BlobChunkMessage
|
||||
| ToolRuntimeMessage.BlobMessage
|
||||
| ToolRuntimeMessage.LogMessage
|
||||
| ToolRuntimeMessage.FileMessage
|
||||
| ToolRuntimeMessage.VariableMessage
|
||||
| ToolRuntimeMessage.RetrieverResourceMessage
|
||||
| None
|
||||
):
|
||||
if message is None:
|
||||
return None
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage as CoreToolInvokeMessage
|
||||
|
||||
if isinstance(message, CoreToolInvokeMessage.TextMessage):
|
||||
return ToolRuntimeMessage.TextMessage(text=message.text)
|
||||
if isinstance(message, CoreToolInvokeMessage.JsonMessage):
|
||||
return ToolRuntimeMessage.JsonMessage(
|
||||
json_object=message.json_object,
|
||||
suppress_output=message.suppress_output,
|
||||
)
|
||||
if isinstance(message, CoreToolInvokeMessage.BlobMessage):
|
||||
return ToolRuntimeMessage.BlobMessage(blob=message.blob)
|
||||
if isinstance(message, CoreToolInvokeMessage.BlobChunkMessage):
|
||||
return ToolRuntimeMessage.BlobChunkMessage(
|
||||
id=message.id,
|
||||
sequence=message.sequence,
|
||||
total_length=message.total_length,
|
||||
blob=message.blob,
|
||||
end=message.end,
|
||||
)
|
||||
if isinstance(message, CoreToolInvokeMessage.FileMessage):
|
||||
return ToolRuntimeMessage.FileMessage(file_marker=message.file_marker)
|
||||
if isinstance(message, CoreToolInvokeMessage.VariableMessage):
|
||||
return ToolRuntimeMessage.VariableMessage(
|
||||
variable_name=message.variable_name,
|
||||
variable_value=message.variable_value,
|
||||
stream=message.stream,
|
||||
)
|
||||
if isinstance(message, CoreToolInvokeMessage.LogMessage):
|
||||
return ToolRuntimeMessage.LogMessage(
|
||||
id=message.id,
|
||||
label=message.label,
|
||||
parent_id=message.parent_id,
|
||||
error=message.error,
|
||||
status=ToolRuntimeMessage.LogMessage.LogStatus(message.status.value),
|
||||
data=dict(message.data),
|
||||
metadata=dict(message.metadata),
|
||||
)
|
||||
if isinstance(message, CoreToolInvokeMessage.RetrieverResourceMessage):
|
||||
retriever_resources = [
|
||||
resource.model_dump() if hasattr(resource, "model_dump") else dict(resource)
|
||||
for resource in message.retriever_resources
|
||||
]
|
||||
return ToolRuntimeMessage.RetrieverResourceMessage(
|
||||
retriever_resources=retriever_resources,
|
||||
context=message.context,
|
||||
)
|
||||
|
||||
raise TypeError(f"unsupported tool message payload: {type(message).__name__}")
|
||||
|
||||
@staticmethod
|
||||
def _map_invocation_exception(exc: Exception, *, provider_name: str) -> ToolNodeError:
|
||||
if isinstance(exc, ToolNodeError):
|
||||
return exc
|
||||
if isinstance(exc, PluginInvokeError):
|
||||
return ToolRuntimeInvocationError(exc.to_user_friendly_error(plugin_name=provider_name))
|
||||
if isinstance(exc, PluginDaemonClientSideError):
|
||||
return ToolRuntimeInvocationError(f"Failed to invoke tool, error: {exc.description}")
|
||||
if isinstance(exc, ToolInvokeError):
|
||||
return ToolRuntimeInvocationError(f"Failed to invoke tool {provider_name}: {exc}")
|
||||
return ToolRuntimeInvocationError(str(exc))
|
||||
|
||||
|
||||
class DifyHumanInputNodeRuntime(HumanInputNodeRuntimeProtocol):
|
||||
def __init__(self, run_context: Mapping[str, Any] | DifyRunContext) -> None:
|
||||
self._run_context = resolve_dify_run_context(run_context)
|
||||
|
||||
def invoke_source(self) -> str:
|
||||
invoke_from = self._run_context.invoke_from
|
||||
if isinstance(invoke_from, str):
|
||||
return invoke_from
|
||||
return str(getattr(invoke_from, "value", invoke_from))
|
||||
|
||||
def apply_delivery_runtime(
|
||||
self,
|
||||
*,
|
||||
methods: Sequence[DeliveryChannelConfig],
|
||||
) -> Sequence[DeliveryChannelConfig]:
|
||||
return [
|
||||
apply_debug_email_recipient(
|
||||
method,
|
||||
enabled=self.invoke_source() == "debugger",
|
||||
user_id=self._run_context.user_id,
|
||||
)
|
||||
for method in methods
|
||||
]
|
||||
|
||||
def console_actor_id(self) -> str | None:
|
||||
return self._run_context.user_id
|
||||
|
||||
|
||||
def build_dify_llm_file_saver(
|
||||
*,
|
||||
run_context: Mapping[str, Any] | DifyRunContext,
|
||||
http_client: HttpClientProtocol,
|
||||
) -> LLMFileSaver:
|
||||
from dify_graph.nodes.llm.file_saver import FileSaverImpl
|
||||
|
||||
return FileSaverImpl(
|
||||
tool_file_manager=DifyToolFileManager(run_context),
|
||||
file_reference_factory=DifyFileReferenceFactory(run_context),
|
||||
http_client=http_client,
|
||||
)
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from collections.abc import Generator, Mapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
|
||||
from dify_graph.entities.graph_config import NodeConfigDict
|
||||
from dify_graph.enums import BuiltinNodeTypes, SystemVariableKey, WorkflowNodeExecutionStatus
|
||||
from dify_graph.node_events import NodeEventBase, NodeRunResult, StreamCompletedEvent
|
||||
@@ -59,7 +60,7 @@ class AgentNode(Node[AgentNodeData]):
|
||||
return "1"
|
||||
|
||||
def populate_start_event(self, event) -> None:
|
||||
dify_ctx = self.require_dify_context()
|
||||
dify_ctx = DifyRunContext.model_validate(self.require_run_context_value(DIFY_RUN_CONTEXT_KEY))
|
||||
event.extras["agent_strategy"] = {
|
||||
"name": self.node_data.agent_strategy_name,
|
||||
"icon": self._presentation_provider.get_icon(
|
||||
@@ -71,7 +72,7 @@ class AgentNode(Node[AgentNodeData]):
|
||||
def _run(self) -> Generator[NodeEventBase, None, None]:
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
|
||||
dify_ctx = self.require_dify_context()
|
||||
dify_ctx = DifyRunContext.model_validate(self.require_run_context_value(DIFY_RUN_CONTEXT_KEY))
|
||||
|
||||
try:
|
||||
strategy = self._strategy_resolver.resolve(
|
||||
@@ -97,6 +98,7 @@ class AgentNode(Node[AgentNodeData]):
|
||||
node_data=self.node_data,
|
||||
strategy=strategy,
|
||||
tenant_id=dify_ctx.tenant_id,
|
||||
user_id=dify_ctx.user_id,
|
||||
app_id=dify_ctx.app_id,
|
||||
invoke_from=dify_ctx.invoke_from,
|
||||
)
|
||||
@@ -106,6 +108,7 @@ class AgentNode(Node[AgentNodeData]):
|
||||
node_data=self.node_data,
|
||||
strategy=strategy,
|
||||
tenant_id=dify_ctx.tenant_id,
|
||||
user_id=dify_ctx.user_id,
|
||||
app_id=dify_ctx.app_id,
|
||||
invoke_from=dify_ctx.invoke_from,
|
||||
for_log=True,
|
||||
|
||||
@@ -14,7 +14,7 @@ from core.agent.plugin_entities import AgentStrategyParameter
|
||||
from core.memory.token_buffer_memory import TokenBufferMemory
|
||||
from core.model_manager import ModelInstance, ModelManager
|
||||
from core.plugin.entities.request import InvokeCredentials
|
||||
from core.provider_manager import ProviderManager
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_provider_manager
|
||||
from core.tools.entities.tool_entities import ToolIdentity, ToolParameter, ToolProviderType
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from dify_graph.enums import SystemVariableKey
|
||||
@@ -38,6 +38,7 @@ class AgentRuntimeSupport:
|
||||
node_data: AgentNodeData,
|
||||
strategy: ResolvedAgentStrategy,
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
app_id: str,
|
||||
invoke_from: Any,
|
||||
for_log: bool = False,
|
||||
@@ -174,7 +175,11 @@ class AgentRuntimeSupport:
|
||||
value = tool_value
|
||||
if parameter.type == AgentStrategyParameter.AgentStrategyParameterType.MODEL_SELECTOR:
|
||||
value = cast(dict[str, Any], value)
|
||||
model_instance, model_schema = self.fetch_model(tenant_id=tenant_id, value=value)
|
||||
model_instance, model_schema = self.fetch_model(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
value=value,
|
||||
)
|
||||
history_prompt_messages = []
|
||||
if node_data.memory:
|
||||
memory = self.fetch_memory(
|
||||
@@ -232,8 +237,14 @@ class AgentRuntimeSupport:
|
||||
|
||||
return TokenBufferMemory(conversation=conversation, model_instance=model_instance)
|
||||
|
||||
def fetch_model(self, *, tenant_id: str, value: dict[str, Any]) -> tuple[ModelInstance, AIModelEntity | None]:
|
||||
provider_manager = ProviderManager()
|
||||
def fetch_model(
|
||||
self,
|
||||
*,
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
value: dict[str, Any],
|
||||
) -> tuple[ModelInstance, AIModelEntity | None]:
|
||||
provider_manager = create_plugin_provider_manager(tenant_id=tenant_id, user_id=user_id)
|
||||
provider_model_bundle = provider_manager.get_provider_model_bundle(
|
||||
tenant_id=tenant_id,
|
||||
provider=value.get("provider", ""),
|
||||
@@ -246,7 +257,7 @@ class AgentRuntimeSupport:
|
||||
)
|
||||
provider_name = provider_model_bundle.configuration.provider.provider
|
||||
model_type_instance = provider_model_bundle.model_type_instance
|
||||
model_instance = ModelManager().get_model_instance(
|
||||
model_instance = ModelManager.for_tenant(tenant_id=tenant_id, user_id=user_id).get_model_instance(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider_name,
|
||||
model_type=ModelType(value.get("model_type", "")),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections.abc import Generator, Mapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
|
||||
from core.datasource.datasource_manager import DatasourceManager
|
||||
from core.datasource.entities.datasource_entities import DatasourceProviderType
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
@@ -50,8 +51,7 @@ class DatasourceNode(Node[DatasourceNodeData]):
|
||||
"""
|
||||
Run the datasource node
|
||||
"""
|
||||
|
||||
dify_ctx = self.require_dify_context()
|
||||
dify_ctx = DifyRunContext.model_validate(self.require_run_context_value(DIFY_RUN_CONTEXT_KEY))
|
||||
node_data = self.node_data
|
||||
variable_pool = self.graph_runtime_state.variable_pool
|
||||
datasource_type_segment = variable_pool.get(["sys", SystemVariableKey.DATASOURCE_TYPE])
|
||||
|
||||
@@ -9,6 +9,7 @@ from collections.abc import Mapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from core.app.app_config.entities import DatasetRetrieveConfigEntity
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
|
||||
from core.rag.data_post_processor.data_post_processor import RerankingModelDict, WeightsDict
|
||||
from core.rag.retrieval.dataset_retrieval import DatasetRetrieval
|
||||
from dify_graph.entities import GraphInitParams
|
||||
@@ -160,7 +161,7 @@ class KnowledgeRetrievalNode(LLMUsageTrackingMixin, Node[KnowledgeRetrievalNodeD
|
||||
def _fetch_dataset_retriever(
|
||||
self, node_data: KnowledgeRetrievalNodeData, variables: dict[str, Any]
|
||||
) -> tuple[list[Source], LLMUsage]:
|
||||
dify_ctx = self.require_dify_context()
|
||||
dify_ctx = DifyRunContext.model_validate(self.require_run_context_value(DIFY_RUN_CONTEXT_KEY))
|
||||
dataset_ids = node_data.dataset_ids
|
||||
query = variables.get("query")
|
||||
attachments = variables.get("attachments")
|
||||
|
||||
@@ -9,9 +9,9 @@ from dify_graph.enums import NodeExecutionType
|
||||
from dify_graph.file import FileTransferMethod
|
||||
from dify_graph.node_events import NodeRunResult
|
||||
from dify_graph.nodes.base.node import Node
|
||||
from dify_graph.nodes.protocols import FileReferenceFactoryProtocol
|
||||
from dify_graph.variables.types import SegmentType
|
||||
from dify_graph.variables.variables import FileVariable
|
||||
from factories import file_factory
|
||||
from factories.variable_factory import build_segment_with_type
|
||||
|
||||
from .entities import ContentType, WebhookData
|
||||
@@ -23,6 +23,13 @@ class TriggerWebhookNode(Node[WebhookData]):
|
||||
node_type = TRIGGER_WEBHOOK_NODE_TYPE
|
||||
execution_type = NodeExecutionType.ROOT
|
||||
|
||||
_file_reference_factory: FileReferenceFactoryProtocol
|
||||
|
||||
def post_init(self) -> None:
|
||||
from core.workflow.node_runtime import DifyFileReferenceFactory
|
||||
|
||||
self._file_reference_factory = DifyFileReferenceFactory(self.graph_init_params.run_context)
|
||||
|
||||
@classmethod
|
||||
def get_default_config(cls, filters: Mapping[str, object] | None = None) -> Mapping[str, object]:
|
||||
return {
|
||||
@@ -70,7 +77,6 @@ class TriggerWebhookNode(Node[WebhookData]):
|
||||
)
|
||||
|
||||
def generate_file_var(self, param_name: str, file: dict):
|
||||
dify_ctx = self.require_dify_context()
|
||||
related_id = file.get("related_id")
|
||||
transfer_method_value = file.get("transfer_method")
|
||||
if transfer_method_value:
|
||||
@@ -84,10 +90,7 @@ class TriggerWebhookNode(Node[WebhookData]):
|
||||
file["datasource_file_id"] = related_id
|
||||
|
||||
try:
|
||||
file_obj = file_factory.build_from_mapping(
|
||||
mapping=file,
|
||||
tenant_id=dify_ctx.tenant_id,
|
||||
)
|
||||
file_obj = self._file_reference_factory.build_from_mapping(mapping=file)
|
||||
file_segment = build_segment_with_type(SegmentType.FILE, file_obj)
|
||||
return FileVariable(name=param_name, value=file_segment.value, selector=[self.id, param_name])
|
||||
except ValueError:
|
||||
|
||||
Reference in New Issue
Block a user