Compare commits

...

30 Commits

Author SHA1 Message Date
-LAN-
85deb8b31c
Merge abacc3768f into d05fee1182 2024-11-15 19:31:46 +08:00
github-actions[bot]
d05fee1182
chore: translate i18n files (#10754)
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Co-authored-by: douxc <7553076+douxc@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: crazywoola <427733928@qq.com>
2024-11-15 19:31:15 +08:00
-LAN-
abacc3768f Updates poetry.lock content hash for consistency
Changes the content hash in poetry.lock to ensure the lock
file's integrity aligns with the updated project dependencies.
No package versions changed in this update.
2024-11-15 11:52:47 +08:00
-LAN-
e31358219c feat(llm-panel): refine variable filtering logic
Introduce `filterJinjia2InputVar` to enhance variable filtering, specifically excluding `arrayFile` types from Jinja2 input variables. This adjustment improves the management of variable types, aligning with expected input capacities and ensuring more reliable configurations. Additionally, support for file variables is enabled in relevant components, broadening functionality and user options.
2024-11-15 11:51:56 +08:00
-LAN-
4e360ec19a refactor(core): decouple LLMNode prompt handling
Moved prompt handling functions out of the `LLMNode` class to improve modularity and separation of concerns. This refactor allows better reuse and testing of prompt-related functions. Adjusted existing logic to fetch queries and handle context and memory configurations more effectively. Updated tests to align with the new structure and ensure continued functionality.
2024-11-15 11:51:56 +08:00
-LAN-
f68d6bd5e2 refactor(node.py): streamline template rendering
Removed the `_render_basic_message` function and integrated its logic directly into the `LLMNode` class. This reduces redundancy and simplifies the handling of message templates by utilizing `convert_template` more directly. This change enhances code readability and maintainability.
2024-11-15 11:51:56 +08:00
-LAN-
b860a893c8 feat(config-prompt): add support for file variables
Extended the `ConfigPromptItem` component to support file variables by including the `isSupportFileVar` prop. Updated `useConfig` hooks to accept `arrayFile` variable types for both input and memory prompt filtering. This enhancement allows handling of file data types seamlessly, improving flexibility in configuring prompts.
2024-11-15 11:51:56 +08:00
-LAN-
0354c7813e fix(file-manager): enforce file extension presence
Added a check to ensure that files have an extension before processing to avoid potential errors. Updated unit tests to reflect this requirement by including extensions in test data. This prevents exceptions from being raised due to missing file extension information.
2024-11-15 11:51:56 +08:00
-LAN-
94794d892e feat: add support for document, video, and audio content
Expanded the system to handle document types across different modules and introduced video and audio content handling in model features. Adjusted the prompt message logic to conditionally process content based on available features, enhancing flexibility in media processing. Added comprehensive error handling in `LLMNode` for better runtime resilience. Updated YAML configuration and unit tests to reflect these changes.
2024-11-15 11:51:56 +08:00
-LAN-
fb94d0b7cf fix: ensure workflow run persistence before refresh
Adds the workflow run object to the database session to guarantee it is persisted prior to refreshing its state. This change resolves potential issues with data consistency and integrity when the workflow run is accessed after operations. References issue #123 for more context.
2024-11-15 11:51:56 +08:00
-LAN-
02c39b2631 fix(file-uploader): resolve file extension logic order
Rearranged the logic in `getFileExtension` to first check for a valid `fileName` before considering `fileMimetype` or `isRemote`. This change ensures that the function prioritizes extracting extensions from file names directly, improving accuracy and handling edge cases more effectively. This update may prevent incorrect file extensions when mimetype is prioritized incorrectly.

Resolves #123.
2024-11-15 11:51:56 +08:00
-LAN-
87f78ff582 feat: enhance image handling in prompt processing
Updated image processing logic to check for model support of vision features, preventing errors when handling images with models that do not support them. Added a test scenario to validate behavior when vision features are absent. This ensures robust image handling and avoids unexpected behavior during image-related prompts.
2024-11-15 11:51:56 +08:00
-LAN-
6872b32c7d fix(node): handle empty text segments gracefully
Ensure that messages are only created from non-empty text segments, preventing potential issues with empty content.

test: add scenario for file variable handling

Introduce a test case for scenarios involving prompt templates with file variables, particularly images, to improve reliability and test coverage. Updated `LLMNodeTestScenario` to use `Sequence` and `Mapping` for more flexible configurations.

Closes #123, relates to #456.
2024-11-15 11:51:56 +08:00
-LAN-
97fab7649b feat(tests): refactor LLMNode tests for clarity
Refactor test scenarios in LLMNode unit tests by introducing a new `LLMNodeTestScenario` class to enhance readability and consistency. This change simplifies the test case management by encapsulating scenario data and reduces redundancy in specifying test configurations. Improves test clarity and maintainability by using a structured approach.
2024-11-15 11:51:56 +08:00
-LAN-
9f0f82cb1c refactor(tests): streamline LLM node prompt message tests
Refactored LLM node tests to enhance clarity and maintainability by creating test scenarios for different file input combinations. This restructuring replaces repetitive code with a more concise approach, improving test coverage and readability.

No functional code changes were made.

References: #123, #456
2024-11-15 11:51:56 +08:00
-LAN-
ef08abafdf Simplify test setup in LLM node tests
Replaced redundant variables in test setup to streamline and align usage of fake data, enhancing readability and maintainability. Adjusted image URL variables to utilize consistent references, ensuring uniformity across test configurations. Also, corrected context variable naming for clarity. No functional impact, purely a refactor for code clarity.
2024-11-15 11:51:56 +08:00
-LAN-
d6c9ab8554 feat(llm_node): allow to use image file directly in the prompt. 2024-11-15 11:51:56 +08:00
-LAN-
bab989e3b3 Remove unnecessary data from log and text properties
Updated the log and text properties in segments to return
empty strings instead of the segment value. This change
prevents potential leakage of sensitive data by ensuring
only non-sensitive information is logged or transformed
into text. Addresses potential security and privacy concerns.
2024-11-15 11:51:56 +08:00
-LAN-
ddc86503dc refactor(model_manager): update parameter type for flexibility
- Changed 'prompt_messages' parameter from list to Sequence for broader input type compatibility.
2024-11-15 11:51:56 +08:00
-LAN-
abad35f700 refactor(memory): use Sequence instead of list for prompt messages
- Improved flexibility by using Sequence instead of list, allowing for broader compatibility with different types of sequences.
- Helps future-proof the method signature by leveraging the more generic Sequence type.
2024-11-15 11:51:56 +08:00
-LAN-
620b0e69f5 fix(dependencies): update Faker version constraint
- Changed the Faker version from caret constraint to tilde constraint for compatibility.
- Updated poetry.lock for changes in pyproject.toml content.
2024-11-15 11:51:56 +08:00
-LAN-
71cf4c7dbf chore(config): remove unnecessary 'frozen' parameter for test
- Simplified app configuration by removing the 'frozen' parameter since it is no longer needed.
- Ensures more flexible handling of config attributes.
2024-11-15 11:51:20 +08:00
-LAN-
47e8a5d4d1 refactor(model_runtime): use Sequence for content in PromptMessage
- Replaced list with Sequence for more flexible content type.
- Improved type consistency by importing from collections.abc.
2024-11-15 11:51:20 +08:00
-LAN-
93bbb194f2 refactor(prompt): enhance type flexibility for prompt messages
- Changed input type from list to Sequence for prompt messages to allow more flexible input types.
- Improved compatibility with functions expecting different iterable types.
2024-11-15 11:51:20 +08:00
-LAN-
2106fc5266 fix(tests): update Azure Rerank Model usage and clean imports 2024-11-15 11:51:20 +08:00
-LAN-
229b146525 feat(errors): add new error classes for unsupported prompt types and memory role prefix requirements 2024-11-15 11:51:20 +08:00
-LAN-
d9fa6f79be refactor: update jinja2_variables and prompt_config to use Sequence and add validators for None handling 2024-11-15 11:51:20 +08:00
-LAN-
4f89214d89 refactor: update stop parameter type to use Sequence instead of list 2024-11-15 11:51:20 +08:00
-LAN-
1fdaea29aa refactor(converter): simplify model credentials validation logic 2024-11-15 11:51:20 +08:00
-LAN-
1397d0000d chore(deps): add faker 2024-11-15 11:51:20 +08:00
79 changed files with 1910 additions and 261 deletions

View File

@ -27,7 +27,6 @@ class DifyConfig(
# read from dotenv format config file
env_file=".env",
env_file_encoding="utf-8",
frozen=True,
# ignore extra attributes
extra="ignore",
)

View File

@ -11,7 +11,7 @@ from core.provider_manager import ProviderManager
class ModelConfigConverter:
@classmethod
def convert(cls, app_config: EasyUIBasedAppConfig, skip_check: bool = False) -> ModelConfigWithCredentialsEntity:
def convert(cls, app_config: EasyUIBasedAppConfig) -> ModelConfigWithCredentialsEntity:
"""
Convert app model config dict to entity.
:param app_config: app config
@ -38,12 +38,8 @@ class ModelConfigConverter:
)
if model_credentials is None:
if not skip_check:
raise ProviderTokenNotInitError(f"Model {model_name} credentials is not initialized.")
else:
model_credentials = {}
if not skip_check:
# check model
provider_model = provider_model_bundle.configuration.get_provider_model(
model=model_config.model, model_type=ModelType.LLM
@ -76,7 +72,7 @@ class ModelConfigConverter:
model_schema = model_type_instance.get_model_schema(model_config.model, model_credentials)
if not skip_check and not model_schema:
if not model_schema:
raise ValueError(f"Model {model_name} not exist.")
return ModelConfigWithCredentialsEntity(

View File

@ -217,6 +217,7 @@ class WorkflowCycleManage:
).total_seconds()
db.session.commit()
db.session.add(workflow_run)
db.session.refresh(workflow_run)
db.session.close()

View File

@ -74,6 +74,8 @@ def to_prompt_message_content(
data = _to_url(f)
else:
data = _to_base64_data_string(f)
if f.extension is None:
raise ValueError("Missing file extension")
return VideoPromptMessageContent(data=data, format=f.extension.lstrip("."))
case _:
raise ValueError("file type f.type is not supported")

View File

@ -1,3 +1,4 @@
from collections.abc import Sequence
from typing import Optional
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
@ -27,7 +28,7 @@ class TokenBufferMemory:
def get_history_prompt_messages(
self, max_token_limit: int = 2000, message_limit: Optional[int] = None
) -> list[PromptMessage]:
) -> Sequence[PromptMessage]:
"""
Get history prompt messages.
:param max_token_limit: max token limit

View File

@ -100,10 +100,10 @@ class ModelInstance:
def invoke_llm(
self,
prompt_messages: list[PromptMessage],
prompt_messages: Sequence[PromptMessage],
model_parameters: Optional[dict] = None,
tools: Sequence[PromptMessageTool] | None = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,

View File

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import Optional
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk
@ -31,7 +32,7 @@ class Callback(ABC):
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
) -> None:
@ -60,7 +61,7 @@ class Callback(ABC):
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
):
@ -90,7 +91,7 @@ class Callback(ABC):
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
) -> None:
@ -120,7 +121,7 @@ class Callback(ABC):
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
) -> None:

View File

@ -1,4 +1,5 @@
from abc import ABC
from collections.abc import Sequence
from enum import Enum
from typing import Optional
@ -57,6 +58,7 @@ class PromptMessageContentType(Enum):
IMAGE = "image"
AUDIO = "audio"
VIDEO = "video"
DOCUMENT = "document"
class PromptMessageContent(BaseModel):
@ -107,7 +109,7 @@ class PromptMessage(ABC, BaseModel):
"""
role: PromptMessageRole
content: Optional[str | list[PromptMessageContent]] = None
content: Optional[str | Sequence[PromptMessageContent]] = None
name: Optional[str] = None
def is_empty(self) -> bool:

View File

@ -87,6 +87,9 @@ class ModelFeature(Enum):
AGENT_THOUGHT = "agent-thought"
VISION = "vision"
STREAM_TOOL_CALL = "stream-tool-call"
DOCUMENT = "document"
VIDEO = "video"
AUDIO = "audio"
class DefaultParameterName(str, Enum):

View File

@ -2,7 +2,7 @@ import logging
import re
import time
from abc import abstractmethod
from collections.abc import Generator, Mapping
from collections.abc import Generator, Mapping, Sequence
from typing import Optional, Union
from pydantic import ConfigDict
@ -48,7 +48,7 @@ class LargeLanguageModel(AIModel):
prompt_messages: list[PromptMessage],
model_parameters: Optional[dict] = None,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -169,7 +169,7 @@ class LargeLanguageModel(AIModel):
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -212,7 +212,7 @@ if you are not sure about the structure.
)
model_parameters.pop("response_format")
stop = stop or []
stop = list(stop) if stop is not None else []
stop.extend(["\n```", "```\n"])
block_prompts = block_prompts.replace("{{block}}", code_block)
@ -408,7 +408,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -479,7 +479,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
) -> Union[LLMResult, Generator]:
@ -601,7 +601,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -647,7 +647,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -694,7 +694,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
@ -742,7 +742,7 @@ if you are not sure about the structure.
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stop: Optional[Sequence[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,

View File

@ -8,6 +8,7 @@ features:
- agent-thought
- stream-tool-call
- vision
- audio
model_properties:
mode: chat
context_size: 128000

View File

@ -1,3 +1,4 @@
from collections.abc import Sequence
from typing import cast
from core.model_runtime.entities import (
@ -14,7 +15,7 @@ from core.prompt.simple_prompt_transform import ModelMode
class PromptMessageUtil:
@staticmethod
def prompt_messages_to_prompt_for_saving(model_mode: str, prompt_messages: list[PromptMessage]) -> list[dict]:
def prompt_messages_to_prompt_for_saving(model_mode: str, prompt_messages: Sequence[PromptMessage]) -> list[dict]:
"""
Prompt messages to prompt for saving.
:param model_mode: model mode

View File

@ -118,11 +118,11 @@ class FileSegment(Segment):
@property
def log(self) -> str:
return str(self.value)
return ""
@property
def text(self) -> str:
return str(self.value)
return ""
class ArrayAnySegment(ArraySegment):
@ -155,3 +155,11 @@ class ArrayFileSegment(ArraySegment):
for item in self.value:
items.append(item.markdown)
return "\n".join(items)
@property
def log(self) -> str:
return ""
@property
def text(self) -> str:
return ""

View File

@ -39,7 +39,14 @@ class VisionConfig(BaseModel):
class PromptConfig(BaseModel):
jinja2_variables: Optional[list[VariableSelector]] = None
jinja2_variables: Sequence[VariableSelector] = Field(default_factory=list)
@field_validator("jinja2_variables", mode="before")
@classmethod
def convert_none_jinja2_variables(cls, v: Any):
if v is None:
return []
return v
class LLMNodeChatModelMessage(ChatModelMessage):
@ -53,7 +60,14 @@ class LLMNodeCompletionModelPromptTemplate(CompletionModelPromptTemplate):
class LLMNodeData(BaseNodeData):
model: ModelConfig
prompt_template: Sequence[LLMNodeChatModelMessage] | LLMNodeCompletionModelPromptTemplate
prompt_config: Optional[PromptConfig] = None
prompt_config: PromptConfig = Field(default_factory=PromptConfig)
memory: Optional[MemoryConfig] = None
context: ContextConfig
vision: VisionConfig = Field(default_factory=VisionConfig)
@field_validator("prompt_config", mode="before")
@classmethod
def convert_none_prompt_config(cls, v: Any):
if v is None:
return PromptConfig()
return v

View File

@ -24,3 +24,11 @@ class LLMModeRequiredError(LLMNodeError):
class NoPromptFoundError(LLMNodeError):
"""Raised when no prompt is found in the LLM configuration."""
class NotSupportedPromptTypeError(LLMNodeError):
"""Raised when the prompt type is not supported."""
class MemoryRolePrefixRequiredError(LLMNodeError):
"""Raised when memory role prefix is required for completion model."""

View File

@ -1,4 +1,5 @@
import json
import logging
from collections.abc import Generator, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Optional, cast
@ -6,21 +7,26 @@ from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEnti
from core.entities.model_entities import ModelStatus
from core.entities.provider_entities import QuotaUnit
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from core.file import FileType, file_manager
from core.helper.code_executor import CodeExecutor, CodeLanguage
from core.memory.token_buffer_memory import TokenBufferMemory
from core.model_manager import ModelInstance, ModelManager
from core.model_runtime.entities import (
AudioPromptMessageContent,
ImagePromptMessageContent,
PromptMessage,
PromptMessageContentType,
TextPromptMessageContent,
VideoPromptMessageContent,
)
from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage
from core.model_runtime.entities.model_entities import ModelType
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
PromptMessageRole,
SystemPromptMessage,
UserPromptMessage,
)
from core.model_runtime.entities.model_entities import ModelFeature, ModelPropertyKey, ModelType
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.model_runtime.utils.encoders import jsonable_encoder
from core.prompt.advanced_prompt_transform import AdvancedPromptTransform
from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig
from core.prompt.utils.prompt_message_util import PromptMessageUtil
from core.variables import (
@ -32,8 +38,9 @@ from core.variables import (
ObjectSegment,
StringSegment,
)
from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.variable_entities import VariableSelector
from core.workflow.entities.variable_pool import VariablePool
from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import InNodeEvent
from core.workflow.nodes.base import BaseNode
@ -62,14 +69,18 @@ from .exc import (
InvalidVariableTypeError,
LLMModeRequiredError,
LLMNodeError,
MemoryRolePrefixRequiredError,
ModelNotExistError,
NoPromptFoundError,
NotSupportedPromptTypeError,
VariableNotFoundError,
)
if TYPE_CHECKING:
from core.file.models import File
logger = logging.getLogger(__name__)
class LLMNode(BaseNode[LLMNodeData]):
_node_data_cls = LLMNodeData
@ -123,17 +134,13 @@ class LLMNode(BaseNode[LLMNodeData]):
# fetch prompt messages
if self.node_data.memory:
query = self.graph_runtime_state.variable_pool.get((SYSTEM_VARIABLE_NODE_ID, SystemVariableKey.QUERY))
if not query:
raise VariableNotFoundError("Query not found")
query = query.text
query = self.node_data.memory.query_prompt_template
else:
query = None
prompt_messages, stop = self._fetch_prompt_messages(
system_query=query,
inputs=inputs,
files=files,
user_query=query,
user_files=files,
context=context,
memory=memory,
model_config=model_config,
@ -141,6 +148,8 @@ class LLMNode(BaseNode[LLMNodeData]):
memory_config=self.node_data.memory,
vision_enabled=self.node_data.vision.enabled,
vision_detail=self.node_data.vision.configs.detail,
variable_pool=self.graph_runtime_state.variable_pool,
jinja2_variables=self.node_data.prompt_config.jinja2_variables,
)
process_data = {
@ -181,6 +190,17 @@ class LLMNode(BaseNode[LLMNodeData]):
)
)
return
except Exception as e:
logger.exception(f"Node {self.node_id} failed to run: {e}")
yield RunCompletedEvent(
run_result=NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
error=str(e),
inputs=node_inputs,
process_data=process_data,
)
)
return
outputs = {"text": result_text, "usage": jsonable_encoder(usage), "finish_reason": finish_reason}
@ -203,8 +223,8 @@ class LLMNode(BaseNode[LLMNodeData]):
self,
node_data_model: ModelConfig,
model_instance: ModelInstance,
prompt_messages: list[PromptMessage],
stop: Optional[list[str]] = None,
prompt_messages: Sequence[PromptMessage],
stop: Optional[Sequence[str]] = None,
) -> Generator[NodeEvent, None, None]:
db.session.close()
@ -519,9 +539,8 @@ class LLMNode(BaseNode[LLMNodeData]):
def _fetch_prompt_messages(
self,
*,
system_query: str | None = None,
inputs: dict[str, str] | None = None,
files: Sequence["File"],
user_query: str | None = None,
user_files: Sequence["File"],
context: str | None = None,
memory: TokenBufferMemory | None = None,
model_config: ModelConfigWithCredentialsEntity,
@ -529,58 +548,146 @@ class LLMNode(BaseNode[LLMNodeData]):
memory_config: MemoryConfig | None = None,
vision_enabled: bool = False,
vision_detail: ImagePromptMessageContent.DETAIL,
) -> tuple[list[PromptMessage], Optional[list[str]]]:
inputs = inputs or {}
variable_pool: VariablePool,
jinja2_variables: Sequence[VariableSelector],
) -> tuple[Sequence[PromptMessage], Optional[Sequence[str]]]:
prompt_messages = []
prompt_transform = AdvancedPromptTransform(with_variable_tmpl=True)
prompt_messages = prompt_transform.get_prompt(
prompt_template=prompt_template,
inputs=inputs,
query=system_query or "",
files=files,
if isinstance(prompt_template, list):
# For chat model
prompt_messages.extend(
_handle_list_messages(
messages=prompt_template,
context=context,
memory_config=memory_config,
jinja2_variables=jinja2_variables,
variable_pool=variable_pool,
vision_detail_config=vision_detail,
)
)
# Get memory messages for chat mode
memory_messages = _handle_memory_chat_mode(
memory=memory,
memory_config=memory_config,
model_config=model_config,
)
stop = model_config.stop
# Extend prompt_messages with memory messages
prompt_messages.extend(memory_messages)
# Add current query to the prompt messages
if user_query:
message = LLMNodeChatModelMessage(
text=user_query,
role=PromptMessageRole.USER,
edition_type="basic",
)
prompt_messages.extend(
_handle_list_messages(
messages=[message],
context="",
jinja2_variables=[],
variable_pool=variable_pool,
vision_detail_config=vision_detail,
)
)
elif isinstance(prompt_template, LLMNodeCompletionModelPromptTemplate):
# For completion model
prompt_messages.extend(
_handle_completion_template(
template=prompt_template,
context=context,
jinja2_variables=jinja2_variables,
variable_pool=variable_pool,
)
)
# Get memory text for completion model
memory_text = _handle_memory_completion_mode(
memory=memory,
memory_config=memory_config,
model_config=model_config,
)
# Insert histories into the prompt
prompt_content = prompt_messages[0].content
if "#histories#" in prompt_content:
prompt_content = prompt_content.replace("#histories#", memory_text)
else:
prompt_content = memory_text + "\n" + prompt_content
prompt_messages[0].content = prompt_content
# Add current query to the prompt message
if user_query:
prompt_content = prompt_messages[0].content.replace("#sys.query#", user_query)
prompt_messages[0].content = prompt_content
else:
errmsg = f"Prompt type {type(prompt_template)} is not supported"
logger.warning(errmsg)
raise NotSupportedPromptTypeError(errmsg)
if vision_enabled and user_files:
file_prompts = []
for file in user_files:
file_prompt = file_manager.to_prompt_message_content(file, image_detail_config=vision_detail)
file_prompts.append(file_prompt)
if (
len(prompt_messages) > 0
and isinstance(prompt_messages[-1], UserPromptMessage)
and isinstance(prompt_messages[-1].content, list)
):
prompt_messages[-1] = UserPromptMessage(content=prompt_messages[-1].content + file_prompts)
else:
prompt_messages.append(UserPromptMessage(content=file_prompts))
# Filter prompt messages
filtered_prompt_messages = []
for prompt_message in prompt_messages:
if isinstance(prompt_message.content, list):
prompt_message_content = []
for content_item in prompt_message.content:
# Skip content if features are not defined
if not model_config.model_schema.features:
if content_item.type != PromptMessageContentType.TEXT:
continue
prompt_message_content.append(content_item)
continue
# Skip content if corresponding feature is not supported
if (
(
content_item.type == PromptMessageContentType.IMAGE
and ModelFeature.VISION not in model_config.model_schema.features
)
or (
content_item.type == PromptMessageContentType.DOCUMENT
and ModelFeature.DOCUMENT not in model_config.model_schema.features
)
or (
content_item.type == PromptMessageContentType.VIDEO
and ModelFeature.VIDEO not in model_config.model_schema.features
)
or (
content_item.type == PromptMessageContentType.AUDIO
and ModelFeature.AUDIO not in model_config.model_schema.features
)
):
continue
prompt_message_content.append(content_item)
if len(prompt_message_content) == 1 and prompt_message_content[0].type == PromptMessageContentType.TEXT:
prompt_message.content = prompt_message_content[0].data
else:
prompt_message.content = prompt_message_content
if prompt_message.is_empty():
continue
if not isinstance(prompt_message.content, str):
prompt_message_content = []
for content_item in prompt_message.content or []:
# Skip image if vision is disabled
if not vision_enabled and content_item.type == PromptMessageContentType.IMAGE:
continue
if isinstance(content_item, ImagePromptMessageContent):
# Override vision config if LLM node has vision config,
# cuz vision detail is related to the configuration from FileUpload feature.
content_item.detail = vision_detail
prompt_message_content.append(content_item)
elif isinstance(
content_item, TextPromptMessageContent | AudioPromptMessageContent | VideoPromptMessageContent
):
prompt_message_content.append(content_item)
if len(prompt_message_content) > 1:
prompt_message.content = prompt_message_content
elif (
len(prompt_message_content) == 1 and prompt_message_content[0].type == PromptMessageContentType.TEXT
):
prompt_message.content = prompt_message_content[0].data
filtered_prompt_messages.append(prompt_message)
if not filtered_prompt_messages:
if len(filtered_prompt_messages) == 0:
raise NoPromptFoundError(
"No prompt found in the LLM configuration. "
"Please ensure a prompt is properly configured before proceeding."
)
stop = model_config.stop
return filtered_prompt_messages, stop
@classmethod
@ -715,3 +822,198 @@ class LLMNode(BaseNode[LLMNodeData]):
}
},
}
def _combine_text_message_with_role(*, text: str, role: PromptMessageRole):
match role:
case PromptMessageRole.USER:
return UserPromptMessage(content=[TextPromptMessageContent(data=text)])
case PromptMessageRole.ASSISTANT:
return AssistantPromptMessage(content=[TextPromptMessageContent(data=text)])
case PromptMessageRole.SYSTEM:
return SystemPromptMessage(content=[TextPromptMessageContent(data=text)])
raise NotImplementedError(f"Role {role} is not supported")
def _render_jinja2_message(
*,
template: str,
jinjia2_variables: Sequence[VariableSelector],
variable_pool: VariablePool,
):
if not template:
return ""
jinjia2_inputs = {}
for jinja2_variable in jinjia2_variables:
variable = variable_pool.get(jinja2_variable.value_selector)
jinjia2_inputs[jinja2_variable.variable] = variable.to_object() if variable else ""
code_execute_resp = CodeExecutor.execute_workflow_code_template(
language=CodeLanguage.JINJA2,
code=template,
inputs=jinjia2_inputs,
)
result_text = code_execute_resp["result"]
return result_text
def _handle_list_messages(
*,
messages: Sequence[LLMNodeChatModelMessage],
context: Optional[str],
jinja2_variables: Sequence[VariableSelector],
variable_pool: VariablePool,
vision_detail_config: ImagePromptMessageContent.DETAIL,
) -> Sequence[PromptMessage]:
prompt_messages = []
for message in messages:
if message.edition_type == "jinja2":
result_text = _render_jinja2_message(
template=message.jinja2_text or "",
jinjia2_variables=jinja2_variables,
variable_pool=variable_pool,
)
prompt_message = _combine_text_message_with_role(text=result_text, role=message.role)
prompt_messages.append(prompt_message)
else:
# Get segment group from basic message
if context:
template = message.text.replace("{#context#}", context)
else:
template = message.text
segment_group = variable_pool.convert_template(template)
# Process segments for images
file_contents = []
for segment in segment_group.value:
if isinstance(segment, ArrayFileSegment):
for file in segment.value:
if file.type in {FileType.IMAGE, FileType.VIDEO, FileType.AUDIO}:
file_content = file_manager.to_prompt_message_content(
file, image_detail_config=vision_detail_config
)
file_contents.append(file_content)
if isinstance(segment, FileSegment):
file = segment.value
if file.type in {FileType.IMAGE, FileType.VIDEO, FileType.AUDIO}:
file_content = file_manager.to_prompt_message_content(
file, image_detail_config=vision_detail_config
)
file_contents.append(file_content)
# Create message with text from all segments
plain_text = segment_group.text
if plain_text:
prompt_message = _combine_text_message_with_role(text=plain_text, role=message.role)
prompt_messages.append(prompt_message)
if file_contents:
# Create message with image contents
prompt_message = UserPromptMessage(content=file_contents)
prompt_messages.append(prompt_message)
return prompt_messages
def _calculate_rest_token(
*, prompt_messages: list[PromptMessage], model_config: ModelConfigWithCredentialsEntity
) -> int:
rest_tokens = 2000
model_context_tokens = model_config.model_schema.model_properties.get(ModelPropertyKey.CONTEXT_SIZE)
if model_context_tokens:
model_instance = ModelInstance(
provider_model_bundle=model_config.provider_model_bundle, model=model_config.model
)
curr_message_tokens = model_instance.get_llm_num_tokens(prompt_messages)
max_tokens = 0
for parameter_rule in model_config.model_schema.parameter_rules:
if parameter_rule.name == "max_tokens" or (
parameter_rule.use_template and parameter_rule.use_template == "max_tokens"
):
max_tokens = (
model_config.parameters.get(parameter_rule.name)
or model_config.parameters.get(str(parameter_rule.use_template))
or 0
)
rest_tokens = model_context_tokens - max_tokens - curr_message_tokens
rest_tokens = max(rest_tokens, 0)
return rest_tokens
def _handle_memory_chat_mode(
*,
memory: TokenBufferMemory | None,
memory_config: MemoryConfig | None,
model_config: ModelConfigWithCredentialsEntity,
) -> Sequence[PromptMessage]:
memory_messages = []
# Get messages from memory for chat model
if memory and memory_config:
rest_tokens = _calculate_rest_token(prompt_messages=[], model_config=model_config)
memory_messages = memory.get_history_prompt_messages(
max_token_limit=rest_tokens,
message_limit=memory_config.window.size if memory_config.window.enabled else None,
)
return memory_messages
def _handle_memory_completion_mode(
*,
memory: TokenBufferMemory | None,
memory_config: MemoryConfig | None,
model_config: ModelConfigWithCredentialsEntity,
) -> str:
memory_text = ""
# Get history text from memory for completion model
if memory and memory_config:
rest_tokens = _calculate_rest_token(prompt_messages=[], model_config=model_config)
if not memory_config.role_prefix:
raise MemoryRolePrefixRequiredError("Memory role prefix is required for completion model.")
memory_text = memory.get_history_prompt_text(
max_token_limit=rest_tokens,
message_limit=memory_config.window.size if memory_config.window.enabled else None,
human_prefix=memory_config.role_prefix.user,
ai_prefix=memory_config.role_prefix.assistant,
)
return memory_text
def _handle_completion_template(
*,
template: LLMNodeCompletionModelPromptTemplate,
context: Optional[str],
jinja2_variables: Sequence[VariableSelector],
variable_pool: VariablePool,
) -> Sequence[PromptMessage]:
"""Handle completion template processing outside of LLMNode class.
Args:
template: The completion model prompt template
context: Optional context string
jinja2_variables: Variables for jinja2 template rendering
variable_pool: Variable pool for template conversion
Returns:
Sequence of prompt messages
"""
prompt_messages = []
if template.edition_type == "jinja2":
result_text = _render_jinja2_message(
template=template.jinja2_text or "",
jinjia2_variables=jinja2_variables,
variable_pool=variable_pool,
)
else:
if context:
template_text = template.text.replace("{#context#}", context)
else:
template_text = template.text
result_text = variable_pool.convert_template(template_text).text
prompt_message = _combine_text_message_with_role(text=result_text, role=PromptMessageRole.USER)
prompt_messages.append(prompt_message)
return prompt_messages

View File

@ -86,12 +86,14 @@ class QuestionClassifierNode(LLMNode):
)
prompt_messages, stop = self._fetch_prompt_messages(
prompt_template=prompt_template,
system_query=query,
user_query=query,
memory=memory,
model_config=model_config,
files=files,
user_files=files,
vision_enabled=node_data.vision.enabled,
vision_detail=node_data.vision.configs.detail,
variable_pool=variable_pool,
jinja2_variables=[],
)
# handle invoke result

61
api/poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@ -932,10 +932,6 @@ files = [
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"},
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
@ -948,14 +944,8 @@ files = [
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"},
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
@ -966,24 +956,8 @@ files = [
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"},
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"},
{file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"},
{file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"},
{file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"},
{file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"},
{file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"},
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
@ -993,10 +967,6 @@ files = [
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"},
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
@ -1008,10 +978,6 @@ files = [
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"},
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
@ -1024,10 +990,6 @@ files = [
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"},
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
@ -1040,10 +1002,6 @@ files = [
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"},
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
@ -2453,6 +2411,21 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "faker"
version = "32.1.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.8"
files = [
{file = "Faker-32.1.0-py3-none-any.whl", hash = "sha256:c77522577863c264bdc9dad3a2a750ad3f7ee43ff8185072e482992288898814"},
{file = "faker-32.1.0.tar.gz", hash = "sha256:aac536ba04e6b7beb2332c67df78485fc29c1880ff723beac6d1efd45e2f10f5"},
]
[package.dependencies]
python-dateutil = ">=2.4"
typing-extensions = "*"
[[package]]
name = "fal-client"
version = "0.5.6"
@ -11078,4 +11051,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "2ba4b464eebc26598f290fa94713acc44c588f902176e6efa80622911d40f0ac"
content-hash = "cf4e0467f622e58b51411ee1d784928962f52dbf877b8ee013c810909a1f07db"

View File

@ -267,6 +267,7 @@ weaviate-client = "~3.21.0"
optional = true
[tool.poetry.group.dev.dependencies]
coverage = "~7.2.4"
faker = "~32.1.0"
pytest = "~8.3.2"
pytest-benchmark = "~4.0.0"
pytest-env = "~1.1.3"

View File

@ -11,7 +11,6 @@ from core.model_runtime.entities.message_entities import (
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.azure_ai_studio.llm.llm import AzureAIStudioLargeLanguageModel
from tests.integration_tests.model_runtime.__mock.azure_ai_studio import setup_azure_ai_studio_mock
@pytest.mark.parametrize("setup_azure_ai_studio_mock", [["chat"]], indirect=True)

View File

@ -4,29 +4,21 @@ import pytest
from core.model_runtime.entities.rerank_entities import RerankResult
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.azure_ai_studio.rerank.rerank import AzureAIStudioRerankModel
from core.model_runtime.model_providers.azure_ai_studio.rerank.rerank import AzureRerankModel
def test_validate_credentials():
model = AzureAIStudioRerankModel()
model = AzureRerankModel()
with pytest.raises(CredentialsValidateFailedError):
model.validate_credentials(
model="azure-ai-studio-rerank-v1",
credentials={"api_key": "invalid_key", "api_base": os.getenv("AZURE_AI_STUDIO_API_BASE")},
query="What is the capital of the United States?",
docs=[
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
"Census, Carson City had a population of 55,274.",
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
"are a political division controlled by the United States. Its capital is Saipan.",
],
score_threshold=0.8,
)
def test_invoke_model():
model = AzureAIStudioRerankModel()
model = AzureRerankModel()
result = model.invoke(
model="azure-ai-studio-rerank-v1",

View File

@ -1,22 +1,61 @@
from collections.abc import Sequence
from typing import Optional
import pytest
from core.app.entities.app_invoke_entities import InvokeFrom
from configs import dify_config
from core.app.entities.app_invoke_entities import InvokeFrom, ModelConfigWithCredentialsEntity
from core.entities.provider_configuration import ProviderConfiguration, ProviderModelBundle
from core.entities.provider_entities import CustomConfiguration, SystemConfiguration
from core.file import File, FileTransferMethod, FileType
from core.model_runtime.entities.message_entities import ImagePromptMessageContent
from core.model_runtime.entities.common_entities import I18nObject
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
ImagePromptMessageContent,
PromptMessage,
PromptMessageRole,
SystemPromptMessage,
TextPromptMessageContent,
UserPromptMessage,
)
from core.model_runtime.entities.model_entities import AIModelEntity, FetchFrom, ModelFeature, ModelType, ProviderModel
from core.model_runtime.entities.provider_entities import ConfigurateMethod, ProviderEntity
from core.model_runtime.model_providers.model_provider_factory import ModelProviderFactory
from core.prompt.entities.advanced_prompt_entities import MemoryConfig
from core.variables import ArrayAnySegment, ArrayFileSegment, NoneSegment
from core.workflow.entities.variable_pool import VariablePool
from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState
from core.workflow.nodes.answer import AnswerStreamGenerateRoute
from core.workflow.nodes.end import EndStreamParam
from core.workflow.nodes.llm.entities import ContextConfig, LLMNodeData, ModelConfig, VisionConfig, VisionConfigOptions
from core.workflow.nodes.llm.entities import (
ContextConfig,
LLMNodeChatModelMessage,
LLMNodeData,
ModelConfig,
VisionConfig,
VisionConfigOptions,
)
from core.workflow.nodes.llm.node import LLMNode
from models.enums import UserFrom
from models.provider import ProviderType
from models.workflow import WorkflowType
from tests.unit_tests.core.workflow.nodes.llm.test_scenarios import LLMNodeTestScenario
class TestLLMNode:
@pytest.fixture
def llm_node(self):
class MockTokenBufferMemory:
def __init__(self, history_messages=None):
self.history_messages = history_messages or []
def get_history_prompt_messages(
self, max_token_limit: int = 2000, message_limit: Optional[int] = None
) -> Sequence[PromptMessage]:
if message_limit is not None:
return self.history_messages[-message_limit * 2 :]
return self.history_messages
@pytest.fixture
def llm_node():
data = LLMNodeData(
title="Test LLM",
model=ModelConfig(provider="openai", name="gpt-3.5-turbo", mode="chat", completion_params={}),
@ -70,7 +109,48 @@ class TestLLMNode:
)
return node
def test_fetch_files_with_file_segment(self, llm_node):
@pytest.fixture
def model_config():
# Create actual provider and model type instances
model_provider_factory = ModelProviderFactory()
provider_instance = model_provider_factory.get_provider_instance("openai")
model_type_instance = provider_instance.get_model_instance(ModelType.LLM)
# Create a ProviderModelBundle
provider_model_bundle = ProviderModelBundle(
configuration=ProviderConfiguration(
tenant_id="1",
provider=provider_instance.get_provider_schema(),
preferred_provider_type=ProviderType.CUSTOM,
using_provider_type=ProviderType.CUSTOM,
system_configuration=SystemConfiguration(enabled=False),
custom_configuration=CustomConfiguration(provider=None),
model_settings=[],
),
provider_instance=provider_instance,
model_type_instance=model_type_instance,
)
# Create and return a ModelConfigWithCredentialsEntity
return ModelConfigWithCredentialsEntity(
provider="openai",
model="gpt-3.5-turbo",
model_schema=AIModelEntity(
model="gpt-3.5-turbo",
label=I18nObject(en_US="GPT-3.5 Turbo"),
model_type=ModelType.LLM,
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
model_properties={},
),
mode="chat",
credentials={},
parameters={},
provider_model_bundle=provider_model_bundle,
)
def test_fetch_files_with_file_segment(llm_node):
file = File(
id="1",
tenant_id="test",
@ -84,7 +164,8 @@ class TestLLMNode:
result = llm_node._fetch_files(selector=["sys", "files"])
assert result == [file]
def test_fetch_files_with_array_file_segment(self, llm_node):
def test_fetch_files_with_array_file_segment(llm_node):
files = [
File(
id="1",
@ -108,18 +189,296 @@ class TestLLMNode:
result = llm_node._fetch_files(selector=["sys", "files"])
assert result == files
def test_fetch_files_with_none_segment(self, llm_node):
def test_fetch_files_with_none_segment(llm_node):
llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], NoneSegment())
result = llm_node._fetch_files(selector=["sys", "files"])
assert result == []
def test_fetch_files_with_array_any_segment(self, llm_node):
def test_fetch_files_with_array_any_segment(llm_node):
llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], ArrayAnySegment(value=[]))
result = llm_node._fetch_files(selector=["sys", "files"])
assert result == []
def test_fetch_files_with_non_existent_variable(self, llm_node):
def test_fetch_files_with_non_existent_variable(llm_node):
result = llm_node._fetch_files(selector=["sys", "files"])
assert result == []
def test_fetch_prompt_messages__vison_disabled(faker, llm_node, model_config):
prompt_template = []
llm_node.node_data.prompt_template = prompt_template
fake_vision_detail = faker.random_element(
[ImagePromptMessageContent.DETAIL.HIGH, ImagePromptMessageContent.DETAIL.LOW]
)
fake_remote_url = faker.url()
files = [
File(
id="1",
tenant_id="test",
type=FileType.IMAGE,
filename="test1.jpg",
transfer_method=FileTransferMethod.REMOTE_URL,
remote_url=fake_remote_url,
)
]
fake_query = faker.sentence()
prompt_messages, _ = llm_node._fetch_prompt_messages(
user_query=fake_query,
user_files=files,
context=None,
memory=None,
model_config=model_config,
prompt_template=prompt_template,
memory_config=None,
vision_enabled=False,
vision_detail=fake_vision_detail,
variable_pool=llm_node.graph_runtime_state.variable_pool,
jinja2_variables=[],
)
assert prompt_messages == [UserPromptMessage(content=fake_query)]
def test_fetch_prompt_messages__basic(faker, llm_node, model_config):
# Setup dify config
dify_config.MULTIMODAL_SEND_IMAGE_FORMAT = "url"
dify_config.MULTIMODAL_SEND_VIDEO_FORMAT = "url"
# Generate fake values for prompt template
fake_assistant_prompt = faker.sentence()
fake_query = faker.sentence()
fake_context = faker.sentence()
fake_window_size = faker.random_int(min=1, max=3)
fake_vision_detail = faker.random_element(
[ImagePromptMessageContent.DETAIL.HIGH, ImagePromptMessageContent.DETAIL.LOW]
)
fake_remote_url = faker.url()
# Setup mock memory with history messages
mock_history = [
UserPromptMessage(content=faker.sentence()),
AssistantPromptMessage(content=faker.sentence()),
UserPromptMessage(content=faker.sentence()),
AssistantPromptMessage(content=faker.sentence()),
UserPromptMessage(content=faker.sentence()),
AssistantPromptMessage(content=faker.sentence()),
]
# Setup memory configuration
memory_config = MemoryConfig(
role_prefix=MemoryConfig.RolePrefix(user="Human", assistant="Assistant"),
window=MemoryConfig.WindowConfig(enabled=True, size=fake_window_size),
query_prompt_template=None,
)
memory = MockTokenBufferMemory(history_messages=mock_history)
# Test scenarios covering different file input combinations
test_scenarios = [
LLMNodeTestScenario(
description="No files",
user_query=fake_query,
user_files=[],
features=[],
vision_enabled=False,
vision_detail=None,
window_size=fake_window_size,
prompt_template=[
LLMNodeChatModelMessage(
text=fake_context,
role=PromptMessageRole.SYSTEM,
edition_type="basic",
),
LLMNodeChatModelMessage(
text="{#context#}",
role=PromptMessageRole.USER,
edition_type="basic",
),
LLMNodeChatModelMessage(
text=fake_assistant_prompt,
role=PromptMessageRole.ASSISTANT,
edition_type="basic",
),
],
expected_messages=[
SystemPromptMessage(content=fake_context),
UserPromptMessage(content=fake_context),
AssistantPromptMessage(content=fake_assistant_prompt),
]
+ mock_history[fake_window_size * -2 :]
+ [
UserPromptMessage(content=fake_query),
],
),
LLMNodeTestScenario(
description="User files",
user_query=fake_query,
user_files=[
File(
tenant_id="test",
type=FileType.IMAGE,
filename="test1.jpg",
transfer_method=FileTransferMethod.REMOTE_URL,
remote_url=fake_remote_url,
)
],
vision_enabled=True,
vision_detail=fake_vision_detail,
features=[ModelFeature.VISION],
window_size=fake_window_size,
prompt_template=[
LLMNodeChatModelMessage(
text=fake_context,
role=PromptMessageRole.SYSTEM,
edition_type="basic",
),
LLMNodeChatModelMessage(
text="{#context#}",
role=PromptMessageRole.USER,
edition_type="basic",
),
LLMNodeChatModelMessage(
text=fake_assistant_prompt,
role=PromptMessageRole.ASSISTANT,
edition_type="basic",
),
],
expected_messages=[
SystemPromptMessage(content=fake_context),
UserPromptMessage(content=fake_context),
AssistantPromptMessage(content=fake_assistant_prompt),
]
+ mock_history[fake_window_size * -2 :]
+ [
UserPromptMessage(
content=[
TextPromptMessageContent(data=fake_query),
ImagePromptMessageContent(data=fake_remote_url, detail=fake_vision_detail),
]
),
],
),
LLMNodeTestScenario(
description="Prompt template with variable selector of File",
user_query=fake_query,
user_files=[],
vision_enabled=False,
vision_detail=fake_vision_detail,
features=[ModelFeature.VISION],
window_size=fake_window_size,
prompt_template=[
LLMNodeChatModelMessage(
text="{{#input.image#}}",
role=PromptMessageRole.USER,
edition_type="basic",
),
],
expected_messages=[
UserPromptMessage(
content=[
ImagePromptMessageContent(data=fake_remote_url, detail=fake_vision_detail),
]
),
]
+ mock_history[fake_window_size * -2 :]
+ [UserPromptMessage(content=fake_query)],
file_variables={
"input.image": File(
tenant_id="test",
type=FileType.IMAGE,
filename="test1.jpg",
transfer_method=FileTransferMethod.REMOTE_URL,
remote_url=fake_remote_url,
)
},
),
LLMNodeTestScenario(
description="Prompt template with variable selector of File without vision feature",
user_query=fake_query,
user_files=[],
vision_enabled=True,
vision_detail=fake_vision_detail,
features=[],
window_size=fake_window_size,
prompt_template=[
LLMNodeChatModelMessage(
text="{{#input.image#}}",
role=PromptMessageRole.USER,
edition_type="basic",
),
],
expected_messages=mock_history[fake_window_size * -2 :] + [UserPromptMessage(content=fake_query)],
file_variables={
"input.image": File(
tenant_id="test",
type=FileType.IMAGE,
filename="test1.jpg",
transfer_method=FileTransferMethod.REMOTE_URL,
remote_url=fake_remote_url,
)
},
),
LLMNodeTestScenario(
description="Prompt template with variable selector of File with video file and vision feature",
user_query=fake_query,
user_files=[],
vision_enabled=True,
vision_detail=fake_vision_detail,
features=[ModelFeature.VISION],
window_size=fake_window_size,
prompt_template=[
LLMNodeChatModelMessage(
text="{{#input.image#}}",
role=PromptMessageRole.USER,
edition_type="basic",
),
],
expected_messages=mock_history[fake_window_size * -2 :] + [UserPromptMessage(content=fake_query)],
file_variables={
"input.image": File(
tenant_id="test",
type=FileType.VIDEO,
filename="test1.mp4",
transfer_method=FileTransferMethod.REMOTE_URL,
remote_url=fake_remote_url,
extension="mp4",
)
},
),
]
for scenario in test_scenarios:
model_config.model_schema.features = scenario.features
for k, v in scenario.file_variables.items():
selector = k.split(".")
llm_node.graph_runtime_state.variable_pool.add(selector, v)
# Call the method under test
prompt_messages, _ = llm_node._fetch_prompt_messages(
user_query=scenario.user_query,
user_files=scenario.user_files,
context=fake_context,
memory=memory,
model_config=model_config,
prompt_template=scenario.prompt_template,
memory_config=memory_config,
vision_enabled=scenario.vision_enabled,
vision_detail=scenario.vision_detail,
variable_pool=llm_node.graph_runtime_state.variable_pool,
jinja2_variables=[],
)
# Verify the result
assert len(prompt_messages) == len(scenario.expected_messages), f"Scenario failed: {scenario.description}"
assert (
prompt_messages == scenario.expected_messages
), f"Message content mismatch in scenario: {scenario.description}"

View File

@ -0,0 +1,25 @@
from collections.abc import Mapping, Sequence
from pydantic import BaseModel, Field
from core.file import File
from core.model_runtime.entities.message_entities import PromptMessage
from core.model_runtime.entities.model_entities import ModelFeature
from core.workflow.nodes.llm.entities import LLMNodeChatModelMessage
class LLMNodeTestScenario(BaseModel):
"""Test scenario for LLM node testing."""
description: str = Field(..., description="Description of the test scenario")
user_query: str = Field(..., description="User query input")
user_files: Sequence[File] = Field(default_factory=list, description="List of user files")
vision_enabled: bool = Field(default=False, description="Whether vision is enabled")
vision_detail: str | None = Field(None, description="Vision detail level if vision is enabled")
features: Sequence[ModelFeature] = Field(default_factory=list, description="List of model features")
window_size: int = Field(..., description="Window size for memory")
prompt_template: Sequence[LLMNodeChatModelMessage] = Field(..., description="Template for prompt messages")
file_variables: Mapping[str, File | Sequence[File]] = Field(
default_factory=dict, description="List of file variables"
)
expected_messages: Sequence[PromptMessage] = Field(..., description="Expected messages after processing")

View File

@ -44,12 +44,6 @@ export const fileUpload: FileUpload = ({
}
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
if (fileMimetype)
return mime.getExtension(fileMimetype) || ''
if (isRemote)
return ''
if (fileName) {
const fileNamePair = fileName.split('.')
const fileNamePairLength = fileNamePair.length
@ -58,6 +52,12 @@ export const getFileExtension = (fileName: string, fileMimetype: string, isRemot
return fileNamePair[fileNamePairLength - 1]
}
if (fileMimetype)
return mime.getExtension(fileMimetype) || ''
if (isRemote)
return ''
return ''
}

View File

@ -144,6 +144,7 @@ const ConfigPromptItem: FC<Props> = ({
onEditionTypeChange={onEditionTypeChange}
varList={varList}
handleAddVariable={handleAddVariable}
isSupportFileVar
/>
)
}

View File

@ -67,6 +67,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleStop,
varInputs,
runResult,
filterJinjia2InputVar,
} = useConfig(id, data)
const model = inputs.model
@ -194,7 +195,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
list={inputs.prompt_config?.jinja2_variables || []}
onChange={handleVarListChange}
onVarNameChange={handleVarNameChange}
filterVar={filterVar}
filterVar={filterJinjia2InputVar}
/>
</Field>
)}
@ -233,6 +234,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
hasSetBlockStatus={hasSetBlockStatus}
nodesOutputVars={availableVars}
availableNodes={availableNodesWithParent}
isSupportFileVar
/>
{inputs.memory.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && (

View File

@ -278,11 +278,15 @@ const useConfig = (id: string, payload: LLMNodeType) => {
}, [inputs, setInputs])
const filterInputVar = useCallback((varPayload: Var) => {
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayFile].includes(varPayload.type)
}, [])
const filterJinjia2InputVar = useCallback((varPayload: Var) => {
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
}, [])
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayFile].includes(varPayload.type)
}, [])
const {
@ -406,6 +410,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
handleRun,
handleStop,
runResult,
filterJinjia2InputVar,
}
}

View File

@ -591,6 +591,10 @@ const translation = {
uploadFromComputerReadError: 'Lesen der Datei fehlgeschlagen, bitte versuchen Sie es erneut.',
fileExtensionNotSupport: 'Dateiendung nicht bedient',
},
license: {
expiring: 'Läuft an einem Tag ab',
expiring_plural: 'Läuft in {{count}} Tagen ab',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethodTip: 'Wenden Sie sich an den Systemadministrator, um eine Authentifizierungsmethode hinzuzufügen.',
usePassword: 'Passwort verwenden',
noLoginMethod: 'Authentifizierungsmethode nicht konfiguriert',
licenseExpired: 'Lizenz abgelaufen',
licenseLostTip: 'Fehler beim Verbinden des Dify-Lizenzservers. Wenden Sie sich an Ihren Administrator, um Dify weiterhin zu verwenden.',
licenseInactive: 'Lizenz inaktiv',
licenseInactiveTip: 'Die Dify Enterprise-Lizenz für Ihren Arbeitsbereich ist inaktiv. Wenden Sie sich an Ihren Administrator, um Dify weiterhin zu verwenden.',
licenseExpiredTip: 'Die Dify Enterprise-Lizenz für Ihren Arbeitsbereich ist abgelaufen. Wenden Sie sich an Ihren Administrator, um Dify weiterhin zu verwenden.',
licenseLost: 'Lizenz verloren',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Art',
binaryFileVariable: 'Variable der Binärdatei',
extractListPlaceholder: 'Geben Sie den Index des Listeneintrags ein, geben Sie \'/\' ein, fügen Sie die Variable ein',
},
code: {
inputVars: 'Eingabevariablen',
@ -618,6 +619,7 @@ const translation = {
filterConditionKey: 'Bedingungsschlüssel filtern',
filterCondition: 'Filter-Bedingung',
selectVariableKeyPlaceholder: 'Untervariablenschlüssel auswählen',
extractsCondition: 'Extrahieren des N-Elements',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
pasteFileLinkInputPlaceholder: 'Introduzca la URL...',
uploadFromComputerLimit: 'El archivo de carga no puede exceder {{size}}',
},
license: {
expiring: 'Caduca en un día',
expiring_plural: 'Caducando en {{count}} días',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethod: 'Método de autenticación no configurado',
setYourAccount: 'Configura tu cuenta',
noLoginMethodTip: 'Póngase en contacto con el administrador del sistema para agregar un método de autenticación.',
licenseInactive: 'Licencia inactiva',
licenseInactiveTip: 'La licencia de Dify Enterprise para su espacio de trabajo está inactiva. Póngase en contacto con su administrador para seguir utilizando Dify.',
licenseExpired: 'Licencia caducada',
licenseLost: 'Licencia perdida',
licenseExpiredTip: 'La licencia de Dify Enterprise para su espacio de trabajo ha caducado. Póngase en contacto con su administrador para seguir utilizando Dify.',
licenseLostTip: 'No se pudo conectar el servidor de licencias de Dife. Póngase en contacto con su administrador para seguir utilizando Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Tipo',
binaryFileVariable: 'Variable de archivo binario',
extractListPlaceholder: 'Introduzca el índice de elementos de la lista, escriba \'/\' insertar variable',
},
code: {
inputVars: 'Variables de entrada',
@ -621,6 +622,7 @@ const translation = {
filterConditionComparisonOperator: 'Operador de comparación de condiciones de filtro',
asc: 'ASC',
selectVariableKeyPlaceholder: 'Seleccione la clave de subvariable',
extractsCondition: 'Extraiga el elemento N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
pasteFileLink: 'پیوند فایل را جایگذاری کنید',
uploadFromComputerLimit: 'آپلود فایل نمی تواند از {{size}} تجاوز کند',
},
license: {
expiring_plural: 'انقضا در {{count}} روز',
expiring: 'انقضا در یک روز',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethod: 'روش احراز هویت پیکربندی نشده است',
noLoginMethodTip: 'لطفا برای افزودن روش احراز هویت با مدیر سیستم تماس بگیرید.',
resetPasswordDesc: 'ایمیلی را که برای ثبت نام در Dify استفاده کرده اید تایپ کنید و ما یک ایمیل بازنشانی رمز عبور برای شما ارسال خواهیم کرد.',
licenseInactive: 'مجوز غیر فعال',
licenseLost: 'مجوز گم شده است',
licenseExpired: 'مجوز منقضی شده است',
licenseExpiredTip: 'مجوز Dify Enterprise برای فضای کاری شما منقضی شده است. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.',
licenseInactiveTip: 'مجوز Dify Enterprise برای فضای کاری شما غیرفعال است. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.',
licenseLostTip: 'اتصال سرور مجوز Dify انجام نشد. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
binaryFileVariable: 'متغیر فایل باینری',
type: 'نوع',
extractListPlaceholder: 'فهرست آیتم لیست را وارد کنید، متغیر درج \'/\' را تایپ کنید',
},
code: {
inputVars: 'متغیرهای ورودی',
@ -618,6 +619,7 @@ const translation = {
filterConditionComparisonValue: 'مقدار شرایط فیلتر',
selectVariableKeyPlaceholder: 'کلید متغیر فرعی را انتخاب کنید',
asc: 'صعودی',
extractsCondition: 'مورد N را استخراج کنید',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
pasteFileLinkInvalid: 'Lien de fichier non valide',
uploadFromComputerLimit: 'Le fichier de téléchargement ne peut pas dépasser {{size}}',
},
license: {
expiring: 'Expirant dans un jour',
expiring_plural: 'Expirant dans {{count}} jours',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethodTip: 'Veuillez contacter ladministrateur système pour ajouter une méthode dauthentification.',
resetPasswordDesc: 'Tapez ladresse e-mail que vous avez utilisée pour vous inscrire sur Dify et nous vous enverrons un e-mail de réinitialisation de mot de passe.',
usePassword: 'Utiliser le mot de passe',
licenseInactiveTip: 'La licence Dify Enterprise de votre espace de travail est inactive. Veuillez contacter votre administrateur pour continuer à utiliser Dify.',
licenseLostTip: 'Échec de la connexion au serveur de licences Dify. Veuillez contacter votre administrateur pour continuer à utiliser Dify.',
licenseExpired: 'Licence expirée',
licenseLost: 'Licence perdue',
licenseExpiredTip: 'La licence Dify Enterprise de votre espace de travail a expiré. Veuillez contacter votre administrateur pour continuer à utiliser Dify.',
licenseInactive: 'Licence inactive',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
binaryFileVariable: 'Variable de fichier binaire',
type: 'Type',
extractListPlaceholder: 'Entrez lindex de lélément de liste, tapez \'/\' insérer la variable',
},
code: {
inputVars: 'Variables de saisie',
@ -618,6 +619,7 @@ const translation = {
limit: 'Haut N',
orderBy: 'Trier par',
filterConditionKey: 'Clé de condition de filtre',
extractsCondition: 'Extraire lélément N',
},
},
tracing: {

View File

@ -613,6 +613,10 @@ const translation = {
fileExtensionNotSupport: 'फ़ाइल एक्सटेंशन समर्थित नहीं है',
uploadFromComputer: 'स्थानीय अपलोड',
},
license: {
expiring: 'एक दिन में समाप्त हो रहा है',
expiring_plural: '{{गिनती}} दिनों में समाप्त हो रहा है',
},
}
export default translation

View File

@ -104,6 +104,12 @@ const translation = {
resetPasswordDesc: 'वह ईमेल टाइप करें जिसका उपयोग आपने Dify पर साइन अप करने के लिए किया था और हम आपको एक पासवर्ड रीसेट ईमेल भेजेंगे।',
withSSO: 'एसएसओ के साथ जारी रखें',
back: 'पीछे',
licenseInactive: 'लाइसेंस निष्क्रिय',
licenseExpired: 'लाइसेंस की समय सीमा समाप्त हो गई',
licenseLost: 'लाइसेंस खो गया',
licenseLostTip: 'Dify लायसेंस सर्वर से कनेक्ट करने में विफल. Dify का उपयोग जारी रखने के लिए कृपया अपने व्यवस्थापक से संपर्क करें.',
licenseInactiveTip: 'आपके कार्यस्थल के लिए डिफाई एंटरप्राइज लाइसेंस निष्क्रिय है। कृपया डिफाई का उपयोग जारी रखने के लिए अपने प्रशासक से संपर्क करें।',
licenseExpiredTip: 'आपके कार्यस्थल के लिए डिफाई एंटरप्राइज लाइसेंस समाप्त हो गया है। कृपया डिफाई का उपयोग जारी रखने के लिए अपने प्रशासक से संपर्क करें।',
}
export default translation

View File

@ -420,6 +420,7 @@ const translation = {
},
type: 'प्रकार',
binaryFileVariable: 'बाइनरी फ़ाइल चर',
extractListPlaceholder: 'सूची आइटम इंडेक्स दर्ज करें, \'/\' इन्सर्ट वेरिएबल टाइप करें',
},
code: {
inputVars: 'इनपुट वेरिएबल्स',
@ -638,6 +639,7 @@ const translation = {
filterConditionComparisonOperator: 'फ़िल्टर शर्त तुलन ऑपरेटर',
selectVariableKeyPlaceholder: 'उप चर कुंजी का चयन करें',
inputVar: 'इनपुट वेरिएबल',
extractsCondition: 'N आइटम निकालें',
},
},
tracing: {

View File

@ -622,6 +622,10 @@ const translation = {
pasteFileLink: 'Incolla il collegamento del file',
uploadFromComputerReadError: 'Lettura del file non riuscita, riprovare.',
},
license: {
expiring_plural: 'Scadenza tra {{count}} giorni',
expiring: 'Scadenza in un giorno',
},
}
export default translation

View File

@ -109,6 +109,12 @@ const translation = {
resetPasswordDesc: 'Digita l\'e-mail che hai utilizzato per registrarti su Dify e ti invieremo un\'e-mail per reimpostare la password.',
noLoginMethodTip: 'Contatta l\'amministratore di sistema per aggiungere un metodo di autenticazione.',
enterYourName: 'Inserisci il tuo nome utente',
licenseLostTip: 'Impossibile connettersi al server licenze Dify. Contatta il tuo amministratore per continuare a utilizzare Dify.',
licenseExpired: 'Licenza scaduta',
licenseLost: 'Licenza persa',
licenseExpiredTip: 'La licenza Dify Enterprise per la tua area di lavoro è scaduta. Contatta il tuo amministratore per continuare a utilizzare Dify.',
licenseInactiveTip: 'La licenza Dify Enterprise per la tua area di lavoro è inattiva. Contatta il tuo amministratore per continuare a utilizzare Dify.',
licenseInactive: 'Licenza inattiva',
}
export default translation

View File

@ -424,6 +424,7 @@ const translation = {
},
binaryFileVariable: 'Variabile file binario',
type: 'Digitare',
extractListPlaceholder: 'Inserisci l\'indice delle voci dell\'elenco, digita \'/\' inserisci la variabile',
},
code: {
inputVars: 'Variabili di Input',
@ -645,6 +646,7 @@ const translation = {
desc: 'DESC',
filterConditionComparisonValue: 'Valore della condizione di filtro',
orderBy: 'Ordina per',
extractsCondition: 'Estrai l\'elemento N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
fileExtensionNotSupport: 'ファイル拡張子はサポートされていません',
pasteFileLinkInvalid: '無効なファイルリンク',
},
license: {
expiring_plural: '有効期限 {{count}} 日',
expiring: '1日で有効期限が切れます',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
sendVerificationCode: '確認コードの送信',
enterYourName: 'ユーザー名を入力してください',
resetPasswordDesc: 'Difyへのサインアップに使用したメールアドレスを入力すると、パスワードリセットメールが送信されます。',
licenseLost: 'ライセンスを失った',
licenseExpiredTip: 'ワークスペースの Dify Enterprise ライセンスの有効期限が切れています。Difyを引き続き使用するには、管理者に連絡してください。',
licenseInactive: 'ライセンスが非アクティブです',
licenseInactiveTip: 'ワークスペースの Dify Enterprise ライセンスが非アクティブです。Difyを引き続き使用するには、管理者に連絡してください。',
licenseExpired: 'ライセンスの有効期限が切れています',
licenseLostTip: 'Difyライセンスサーバーへの接続に失敗しました。続けてDifyを使用するために管理者に連絡してください。',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'タイプ',
binaryFileVariable: 'バイナリファイル変数',
extractListPlaceholder: 'リスト項目のインデックスを入力し、変数を挿入 \'/\' と入力します',
},
code: {
inputVars: '入力変数',
@ -619,6 +620,7 @@ const translation = {
filterConditionComparisonOperator: 'フィルター条件を比較オペレーター',
inputVar: '入力変数',
desc: 'DESC',
extractsCondition: 'N個のアイテムを抽出します',
},
},
tracing: {

View File

@ -587,6 +587,10 @@ const translation = {
uploadFromComputerLimit: '업로드 파일은 {{size}}를 초과할 수 없습니다.',
uploadFromComputerUploadError: '파일 업로드에 실패했습니다. 다시 업로드하십시오.',
},
license: {
expiring_plural: '{{count}}일 후에 만료',
expiring: '하루 후에 만료',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
enterYourName: '사용자 이름을 입력해 주세요',
noLoginMethodTip: '인증 방법을 추가하려면 시스템 관리자에게 문의하십시오.',
resetPasswordDesc: 'Dify에 가입할 때 사용한 이메일을 입력하면 비밀번호 재설정 이메일을 보내드립니다.',
licenseInactiveTip: '작업 영역에 대한 Dify Enterprise 라이선스가 비활성 상태입니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.',
licenseLost: '라이센스 분실',
licenseLostTip: 'Dify 라이선스 서버에 연결하지 못했습니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.',
licenseInactive: 'License Inactive(라이선스 비활성)',
licenseExpired: '라이센스가 만료되었습니다.',
licenseExpiredTip: '작업 영역에 대한 Dify Enterprise 라이선스가 만료되었습니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: '형',
binaryFileVariable: '바이너리 파일 변수',
extractListPlaceholder: '목록 항목 인덱스 입력, \'/\' 변수 삽입',
},
code: {
inputVars: '입력 변수',
@ -618,6 +619,7 @@ const translation = {
orderBy: '정렬 기준',
selectVariableKeyPlaceholder: '하위 변수 키 선택',
filterConditionComparisonOperator: '필터 조건 비교 연산자',
extractsCondition: 'N 항목을 추출합니다.',
},
},
tracing: {

View File

@ -609,6 +609,10 @@ const translation = {
fileExtensionNotSupport: 'Rozszerzenie pliku nie jest obsługiwane',
uploadFromComputer: 'Przesyłanie lokalne',
},
license: {
expiring_plural: 'Wygasa za {{count}} dni',
expiring: 'Wygasa w ciągu jednego dnia',
},
}
export default translation

View File

@ -104,6 +104,12 @@ const translation = {
or: 'LUB',
noLoginMethodTip: 'Skontaktuj się z administratorem systemu, aby dodać metodę uwierzytelniania.',
noLoginMethod: 'Nie skonfigurowano metody uwierzytelniania',
licenseLost: 'Utrata licencji',
licenseExpired: 'Licencja wygasła',
licenseInactive: 'Licencja nieaktywna',
licenseExpiredTip: 'Licencja Dify Enterprise dla Twojego obszaru roboczego wygasła. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.',
licenseLostTip: 'Nie udało się nawiązać połączenia z serwerem licencji Dify. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.',
licenseInactiveTip: 'Licencja Dify Enterprise dla Twojego obszaru roboczego jest nieaktywna. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Typ',
binaryFileVariable: 'Binarna zmienna pliku',
extractListPlaceholder: 'Wprowadź indeks elementu listy, wpisz "/" wstaw zmienną',
},
code: {
inputVars: 'Zmienne wejściowe',
@ -618,6 +619,7 @@ const translation = {
filterCondition: 'Stan filtra',
filterConditionComparisonValue: 'Wartość warunku filtru',
selectVariableKeyPlaceholder: 'Wybierz klucz zmiennej podrzędnej',
extractsCondition: 'Wyodrębnij element N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
uploadFromComputerLimit: 'Carregar arquivo não pode exceder {{size}}',
uploadFromComputerUploadError: 'Falha no upload do arquivo, faça o upload novamente.',
},
license: {
expiring: 'Expirando em um dia',
expiring_plural: 'Expirando em {{count}} dias',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
resetPasswordDesc: 'Digite o e-mail que você usou para se inscrever no Dify e enviaremos um e-mail de redefinição de senha.',
sendVerificationCode: 'Enviar código de verificação',
usePassword: 'Usar senha',
licenseInactiveTip: 'A licença do Dify Enterprise para seu espaço de trabalho está inativa. Entre em contato com o administrador para continuar usando o Dify.',
licenseLostTip: 'Falha ao conectar o servidor de licenças Dify. Entre em contato com o administrador para continuar usando o Dify.',
licenseExpired: 'Licença expirada',
licenseLost: 'Licença perdida',
licenseInactive: 'Licença inativa',
licenseExpiredTip: 'A licença do Dify Enterprise para seu espaço de trabalho expirou. Entre em contato com o administrador para continuar usando o Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Tipo',
binaryFileVariable: 'Variável de arquivo binário',
extractListPlaceholder: 'Insira o índice do item da lista, digite \'/\' inserir variável',
},
code: {
inputVars: 'Variáveis de entrada',
@ -618,6 +619,7 @@ const translation = {
filterConditionKey: 'Chave de condição do filtro',
filterConditionComparisonOperator: 'Operador de comparação de condição de filtro',
filterConditionComparisonValue: 'Valor da condição do filtro',
extractsCondition: 'Extraia o item N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
uploadFromComputerLimit: 'Încărcarea fișierului nu poate depăși {{size}}',
pasteFileLink: 'Lipiți linkul fișierului',
},
license: {
expiring: 'Expiră într-o zi',
expiring_plural: 'Expiră în {{count}} zile',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
or: 'SAU',
resetPasswordDesc: 'Tastați e-mailul pe care l-ați folosit pentru a vă înscrie pe Dify și vă vom trimite un e-mail de resetare a parolei.',
changePasswordBtn: 'Setați o parolă',
licenseLostTip: 'Nu s-a reușit conectarea serverului de licențe Dify. Contactați administratorul pentru a continua să utilizați Dify.',
licenseInactive: 'Licență inactivă',
licenseInactiveTip: 'Licența Dify Enterprise pentru spațiul de lucru este inactivă. Contactați administratorul pentru a continua să utilizați Dify.',
licenseExpired: 'Licență expirată',
licenseLost: 'Licență pierdută',
licenseExpiredTip: 'Licența Dify Enterprise pentru spațiul de lucru a expirat. Contactați administratorul pentru a continua să utilizați Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Tip',
binaryFileVariable: 'Variabilă de fișier binar',
extractListPlaceholder: 'Introduceți indexul elementelor din listă, tastați "/" inserați variabila',
},
code: {
inputVars: 'Variabile de intrare',
@ -618,6 +619,7 @@ const translation = {
limit: 'N de sus',
filterConditionComparisonValue: 'Valoare Stare filtrare',
asc: 'ASC',
extractsCondition: 'Extrageți elementul N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
uploadFromComputerLimit: 'Файл загрузки не может превышать {{size}}',
uploadFromComputerUploadError: 'Загрузка файла не удалась, пожалуйста, загрузите еще раз.',
},
license: {
expiring: 'Срок действия истекает за один день',
expiring_plural: 'Срок действия истекает через {{count}} дней',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethodTip: 'Обратитесь к системному администратору, чтобы добавить метод аутентификации.',
resetPasswordDesc: 'Введите адрес электронной почты, который вы использовали для регистрации в Dify, и мы отправим вам электронное письмо для сброса пароля.',
or: 'ИЛИ',
licenseInactive: 'Лицензия неактивна',
licenseLostTip: 'Не удалось подключить сервер лицензий Dify. Обратитесь к своему администратору, чтобы продолжить использование Dify.',
licenseExpired: 'Срок действия лицензии истек',
licenseLost: 'Утеряна лицензия',
licenseInactiveTip: 'Лицензия Dify Enterprise для рабочего пространства неактивна. Обратитесь к своему администратору, чтобы продолжить использование Dify.',
licenseExpiredTip: 'Срок действия лицензии Dify Enterprise для рабочего пространства истек. Обратитесь к своему администратору, чтобы продолжить использование Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Тип',
binaryFileVariable: 'Переменная двоичного файла',
extractListPlaceholder: 'Введите индекс элемента списка, введите \'/\' вставьте переменную',
},
code: {
inputVars: 'Входные переменные',
@ -618,6 +619,7 @@ const translation = {
filterConditionKey: 'Ключ условия фильтра',
selectVariableKeyPlaceholder: 'Выбор ключа подпеременной',
filterConditionComparisonValue: 'Значение условия фильтра',
extractsCondition: 'Извлечение элемента N',
},
},
tracing: {

View File

@ -38,6 +38,10 @@ const translation = {
duplicate: 'Podvoji',
rename: 'Preimenuj',
audioSourceUnavailable: 'Zvočni vir ni na voljo',
copyImage: 'Kopiraj sliko',
openInNewTab: 'Odpri v novem zavihku',
zoomOut: 'Pomanjšanje',
zoomIn: 'Povečava',
},
errorMsg: {
fieldRequired: '{{field}} je obvezno',
@ -576,5 +580,220 @@ const translation = {
failed: 'Ustvarjanje oznake ni uspelo',
},
},
dataSource: {
notion: {
selector: {
pageSelected: 'Izbrane strani',
addPages: 'Dodajanje strani',
searchPages: 'Iskanje strani ...',
noSearchResult: 'Ni rezultatov iskanja',
preview: 'PREDOGLED',
},
connected: 'Povezani',
remove: 'Odstrani',
addWorkspace: 'Dodajanje delovnega prostora',
connectedWorkspace: 'Povezani delovni prostor',
description: 'Uporaba pojma kot vira podatkov za znanje.',
disconnected: 'Odklopi',
pagesAuthorized: 'Dovoljene strani',
title: 'Pojem',
changeAuthorizedPages: 'Spreminjanje pooblaščenih strani',
sync: 'Sinhroniziranje',
},
website: {
active: 'Dejaven',
configuredCrawlers: 'Konfigurirani pajki',
title: 'Spletna stran',
inactive: 'Neaktiven',
description: 'Uvozite vsebino s spletnih mest s spletnim pajkom.',
with: 'S',
},
add: 'Dodajanje vira podatkov',
connect: 'Povezati',
configure: 'Konfigurirati',
},
plugin: {
serpapi: {
apiKeyPlaceholder: 'Vnesite ključ API',
apiKey: 'API ključ',
keyFrom: 'Pridobite svoj ključ SerpAPI na strani računa SerpAPI',
},
},
apiBasedExtension: {
selector: {
placeholder: 'Prosimo, izberite razširitev API-ja',
manage: 'Upravljanje razširitve API',
title: 'Razširitev API-ja',
},
modal: {
name: {
placeholder: 'Prosimo, vnesite ime',
title: 'Ime',
},
apiEndpoint: {
title: 'Končna točka API-ja',
placeholder: 'Prosimo, vnesite končno točko API-ja',
},
apiKey: {
lengthError: 'Dolžina ključa API ne sme biti manjša od 5 znakov',
title: 'Ključ API-ja',
placeholder: 'Prosimo, vnesite API-ključ',
},
editTitle: 'Uredi razširitev API-ja',
title: 'Dodajanje razširitve API-ja',
},
type: 'Vrsta',
link: 'Preberite, kako razvijete lastno razširitev API-ja.',
title: 'Razširitve API zagotavljajo centralizirano upravljanje API, kar poenostavlja konfiguracijo za enostavno uporabo v aplikacijah Dify.',
linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension',
add: 'Dodajanje razširitve API-ja',
},
about: {
updateNow: 'Posodobi zdaj',
nowAvailable: 'Dify {{version}} je zdaj na voljo.',
latestAvailable: 'Dify {{version}} je najnovejša različica, ki je na voljo.',
changeLog: 'Dnevnik sprememb',
},
appMenus: {
apiAccess: 'Dostop do API-ja',
logs: 'Dnevniki',
logAndAnn: 'Dnevniki & Ann.',
promptEng: 'Orkester',
overview: 'Spremljanje',
},
environment: {
development: 'RAZVOJ',
testing: 'PREIZKUŠANJE',
},
appModes: {
completionApp: 'Generator besedila',
chatApp: 'Aplikacija za klepet',
},
datasetMenus: {
documents: 'Dokumentov',
settings: 'Nastavitve',
hitTesting: 'Testiranje pridobivanja',
emptyTip: 'Znanje ni bilo povezano, prosimo, pojdite na aplikacijo ali vtičnik, da dokončate združenje.',
viewDoc: 'Oglejte si dokumentacijo',
relatedApp: 'Povezane aplikacije',
},
voiceInput: {
notAllow: 'Mikrofon ni pooblaščen',
speaking: 'Spregovorite zdaj ...',
converting: 'Pretvorba v besedilo ...',
},
modelName: {
'claude-2': 'Claude-2',
'gpt-4-32k': 'GPT-4-32K',
'text-embedding-ada-002': 'Vdelava besedila-Ada-002',
'gpt-4': 'GPT-4',
'whisper-1': 'Šepet-1',
'claude-instant-1': 'Claude-Instant',
'text-davinci-003': 'Besedilo-Davinci-003',
'gpt-3.5-turbo-16k': 'GPT-3.5-Turbo-16K',
'gpt-3.5-turbo': 'GPT-3.5-Turbo',
},
chat: {
citation: {
vectorHash: 'Vektorska razpršitev:',
hitScore: 'Rezultat pridobivanja:',
linkToDataset: 'Povezava do znanja',
hitCount: 'Število pridobivanja:',
characters: 'Znakov:',
title: 'CITATI',
},
conversationNameCanNotEmpty: 'Zahtevano ime pogovora',
inputPlaceholder: 'Pogovorite se z botom',
renameConversation: 'Preimenovanje pogovora',
conversationName: 'Ime pogovora',
conversationNamePlaceholder: 'Prosimo, vnesite ime pogovora',
},
promptEditor: {
context: {
item: {
desc: 'Vstavljanje predloge konteksta',
title: 'Kontekstu',
},
modal: {
footer: 'Kontekste lahko upravljate v spodnjem razdelku Kontekst.',
title: '{{num}} Znanje v kontekstu',
add: 'Dodajanje konteksta',
},
},
history: {
item: {
desc: 'Vstavljanje predloge zgodovinskega sporočila',
title: 'Zgodovina pogovorov',
},
modal: {
title: 'PRIMER',
edit: 'Urejanje imen vlog v pogovoru',
assistant: 'Zdravo! Kako vam lahko pomagam danes?',
user: 'Zdravo',
},
},
variable: {
item: {
desc: 'Vstavljanje spremenljivk in zunanjih orodij',
title: 'Spremenljivke in zunanja orodja',
},
outputToolDisabledItem: {
title: 'Spremenljivke',
desc: 'Vstavljanje spremenljivk',
},
modal: {
addTool: 'Novo orodje',
add: 'Nova spremenljivka',
},
},
query: {
item: {
title: 'Poizvedba',
desc: 'Vstavljanje predloge uporabniške poizvedbe',
},
},
existed: 'Že obstaja v pozivu',
placeholder: 'Tukaj vnesite svojo pozivno besedo, vnesite \'{\' za vstavljanje spremenljivke, vnesite \'/\', da vstavite blok vsebine',
},
imageUploader: {
pasteImageLinkInvalid: 'Neveljavna povezava do slike',
uploadFromComputerLimit: 'Nalaganje slik ne sme presegati {{size}} MB',
uploadFromComputerUploadError: 'Nalaganje slike ni uspelo, naložite ga znova.',
pasteImageLink: 'Prilepi povezavo do slike',
imageUpload: 'Nalaganje slik',
uploadFromComputer: 'Naloži iz računalnika',
pasteImageLinkInputPlaceholder: 'Tukaj prilepi povezavo do slike',
uploadFromComputerReadError: 'Branje slik ni uspelo, poskusite znova.',
},
fileUploader: {
fileExtensionNotSupport: 'Datotečna pripona ni podprta',
pasteFileLinkInvalid: 'Neveljavna povezava do datoteke',
pasteFileLink: 'Prilepi povezavo do datoteke',
pasteFileLinkInputPlaceholder: 'Vnesite URL ...',
uploadFromComputerUploadError: 'Nalaganje datoteke ni uspelo, naložite ga znova.',
uploadFromComputer: 'Lokalno nalaganje',
uploadFromComputerLimit: 'Nalaganje {{type}} ne sme presegati {{size}}',
uploadFromComputerReadError: 'Branje datoteke ni uspelo, poskusite znova.',
},
tag: {
addTag: 'Dodajanje oznak',
delete: 'Brisanje oznake',
manageTags: 'Upravljanje oznak',
addNew: 'Dodajanje nove oznake',
placeholder: 'Vse oznake',
failed: 'Ustvarjanje oznake ni uspelo',
editTag: 'Urejanje oznak',
created: 'Oznaka je bila uspešno ustvarjena',
noTagYet: 'Še ni oznak',
create: 'Ustvariti',
deleteTip: 'Oznaka se uporablja, jo izbrišite?',
noTag: 'Brez oznak',
selectorPlaceholder: 'Vnesite za iskanje ali ustvarjanje',
},
license: {
expiring_plural: 'Poteče v {{count}} dneh',
expiring: 'Poteče v enem dnevu',
},
}
export default translation

View File

@ -152,6 +152,7 @@ const translation = {
indexSettingTip: 'Če želite spremeniti način indeksiranja in model vdelave, pojdite na ',
retrievalSettingTip: 'Če želite spremeniti nastavitve iskanja, pojdite na ',
datasetSettingLink: 'nastavitve Znanja.',
maxLengthCheck: 'Največja dolžina kosa mora biti manjša od 4000',
},
stepThree: {
creationTitle: '🎉 Znanje ustvarjeno',

View File

@ -55,6 +55,7 @@ const translation = {
passwordEmpty: 'Geslo je obvezno',
passwordLengthInValid: 'Geslo mora vsebovati vsaj 8 znakov',
passwordInvalid: 'Geslo mora vsebovati črke in številke, dolžina pa mora biti več kot 8 znakov',
registrationNotAllowed: 'Računa ni mogoče najti. Za registracijo se obrnite na skrbnika sistema.',
},
license: {
tip: 'Preden začnete z Dify Community Edition, preberite GitHub',
@ -70,6 +71,40 @@ const translation = {
activated: 'Prijavite se zdaj',
adminInitPassword: 'Geslo za inicializacijo administratorja',
validate: 'Potrdi',
checkCode: {
emptyCode: 'Koda je obvezna',
verificationCodePlaceholder: 'Vnesite 6-mestno kodo',
resend: 'Poslati',
verificationCode: 'Koda za preverjanje',
tips: 'Kodo za preverjanje pošljemo na <strong>{{email}}</strong>',
verify: 'Preveriti',
validTime: 'Upoštevajte, da je koda veljavna 5 minut',
checkYourEmail: 'Preverjanje e-pošte',
didNotReceiveCode: 'Niste prejeli kode?',
invalidCode: 'Neveljavna koda',
useAnotherMethod: 'Uporabite drug način',
},
useVerificationCode: 'Uporaba kode za preverjanje',
licenseInactive: 'Licenca je neaktivna',
changePasswordBtn: 'Nastavitev gesla',
licenseExpired: 'Licenca je potekla',
resetPassword: 'Ponastavi geslo',
back: 'Hrbet',
backToLogin: 'Nazaj na prijavo',
enterYourName: 'Prosimo, vnesite svoje uporabniško ime',
licenseLost: 'Izgubljena licenca',
licenseExpiredTip: 'Licenca za Dify Enterprise za vaš delovni prostor je potekla. Če želite še naprej uporabljati Dify, se obrnite na skrbnika.',
usePassword: 'Uporaba gesla',
sendVerificationCode: 'Pošlji kodo za preverjanje',
resetPasswordDesc: 'Vnesite e-poštni naslov, ki ste ga uporabili za prijavo na Dify, in poslali vam bomo e-poštno sporočilo za ponastavitev gesla.',
setYourAccount: 'Nastavitev računa',
noLoginMethodTip: 'Obrnite se na skrbnika sistema, da dodate način preverjanja pristnosti.',
or: 'ALI',
noLoginMethod: 'Način preverjanja pristnosti ni konfiguriran',
continueWithCode: 'Nadaljujte s kodo',
withSSO: 'Nadaljujte z enotno prijavo',
licenseLostTip: 'Povezava z licenčnim strežnikom Dify ni uspela. Če želite še naprej uporabljati Dify, se obrnite na skrbnika.',
licenseInactiveTip: 'Licenca Dify Enterprise za vaš delovni prostor je neaktivna. Če želite še naprej uporabljati Dify, se obrnite na skrbnika.',
}
export default translation

View File

@ -95,6 +95,10 @@ const translation = {
addParallelNode: 'Dodaj vzporedno vozlišče',
parallel: 'VZPOREDNO',
branch: 'VEJA',
fileUploadTip: 'Funkcije nalaganja slik so nadgrajene na nalaganje datotek.',
featuresDocLink: 'Izvedi več',
featuresDescription: 'Izboljšajte uporabniško izkušnjo spletne aplikacije',
ImageUploadLegacyTip: 'Zdaj lahko ustvarite spremenljivke vrste datoteke v začetnem obrazcu. V prihodnje ne bomo več podpirali funkcije nalaganja slik.',
},
env: {
envPanelTitle: 'Spremenljivke okolja',
@ -554,6 +558,506 @@ const translation = {
tracing: {
stopBy: 'Ustavljeno s strani {{user}}',
},
chatVariable: {
modal: {
type: 'Vrsta',
objectValue: 'Privzeta vrednost',
description: 'Opis',
editTitle: 'Urejanje spremenljivke pogovora',
namePlaceholder: 'Ime spremenljivke',
valuePlaceholder: 'Privzeta vrednost, pustite prazno, da ni nastavljeno',
title: 'Dodajanje spremenljivke pogovora',
editInJSON: 'Urejanje v JSON',
value: 'Privzeta vrednost',
oneByOne: 'Dodajanje enega za drugim',
objectKey: 'Ključ',
objectType: 'Vrsta',
arrayValue: 'Vrednost',
name: 'Ime',
descriptionPlaceholder: 'Opis spremenljivke',
editInForm: 'Uredi v obrazcu',
addArrayValue: 'Dodajanje vrednosti',
},
storedContent: 'Shranjena vsebina',
updatedAt: 'Posodobljeno na',
panelTitle: 'Spremenljivke pogovora',
button: 'Dodajanje spremenljivke',
panelDescription: 'Spremenljivke pogovora se uporabljajo za shranjevanje interaktivnih informacij, ki si jih mora LLM zapomniti, vključno z zgodovino pogovorov, naloženimi datotekami, uporabniškimi nastavitvami. So branje in pisanje.',
docLink: 'Če želite izvedeti več, obiščite naše dokumente.',
},
changeHistory: {
nodeChange: 'Blokiranje spremenjeno',
placeholder: 'Ničesar še niste spremenili',
nodeDescriptionChange: 'Opis bloka je bil spremenjen',
nodePaste: 'Blokiranje lepljenja',
noteDelete: 'Opomba izbrisana',
nodeDragStop: 'Blok premaknjen',
nodeConnect: 'Blok povezan',
sessionStart: 'Začetek seje',
nodeDelete: 'Blokiraj izbrisane',
stepBackward_other: '{{count}} stopi nazaj',
hint: 'Namig',
noteAdd: 'Opomba dodana',
clearHistory: 'Počisti zgodovino',
stepForward_one: '{{count}} korak naprej',
stepBackward_one: '{{count}} korak nazaj',
nodeAdd: 'Blokiranje dodano',
noteChange: 'Opomba spremenjena',
hintText: 'Dejanjem urejanja se sledi v zgodovini sprememb, ki je shranjena v napravi za čas trajanja te seje. Ta zgodovina bo izbrisana, ko zapustite urejevalnik.',
stepForward_other: '{{count}} koraki naprej',
edgeDelete: 'Blok je prekinjen.',
nodeTitleChange: 'Naslov bloka spremenjen',
nodeResize: 'Spremeni velikost bloka',
title: 'Zgodovina sprememb',
currentState: 'Trenutno stanje',
},
errorMsg: {
fields: {
code: 'Koda',
variableValue: 'Vrednost spremenljivke',
visionVariable: 'Spremenljivka vida',
model: 'Model',
rerankModel: 'Ponovno razvrsti model',
variable: 'Ime spremenljivke',
},
invalidJson: '{{field}} je neveljaven JSON',
invalidVariable: 'Neveljavna spremenljivka',
authRequired: 'Dovoljenje je potrebno',
fieldRequired: '{{field}} je obvezno',
rerankModelRequired: 'Preden vklopite Rerank Model, preverite, ali je bil model uspešno konfiguriran v nastavitvah.',
},
singleRun: {
startRun: 'Začni zagnati',
running: 'Tek',
testRunIteration: 'Ponovitev preskusnega zagona',
iteration: 'Ponovitev',
back: 'Hrbet',
testRun: 'Preskusni zagon',
},
tabs: {
'blocks': 'Bloki',
'workflowTool': 'Potek dela',
'transform': 'Preoblikovanje',
'question-understand': 'Vprašanje razumeti',
'builtInTool': 'Vgrajeno',
'allTool': 'Ves',
'tools': 'Orodja',
'logic': 'Logika',
'searchBlock': 'Iskalni blok',
'noResult': 'Ni najdenega ujemanja',
'customTool': 'Običaj',
'utilities': 'Utilities',
'searchTool': 'Orodje za iskanje',
},
blocks: {
'variable-aggregator': 'Spremenljivi agregator',
'code': 'Koda',
'parameter-extractor': 'Ekstraktor parametrov',
'llm': 'LLM',
'knowledge-retrieval': 'Pridobivanje znanja',
'answer': 'Odgovoriti',
'end': 'Konec',
'document-extractor': 'Ekstraktor dokumentov',
'assigner': 'Dodeljevalnik spremenljivke',
'iteration-start': 'Začetek ponovitve',
'template-transform': 'Predloga',
'iteration': 'Ponovitev',
'start': 'Začetek',
'if-else': 'IF/ELSE',
'list-operator': 'Operater seznama',
'http-request': 'Zahteva HTTP',
'variable-assigner': 'Spremenljivi agregator',
'question-classifier': 'Klasifikator vprašanj',
},
blocksAbout: {
'document-extractor': 'Uporablja se za razčlenjevanje naloženih dokumentov v besedilno vsebino, ki je zlahka razumljiva LLM.',
'list-operator': 'Uporablja se za filtriranje ali razvrščanje vsebine matrike.',
'template-transform': 'Pretvorite podatke v niz s sintakso predloge Jinja',
'question-classifier': 'Določite pogoje razvrščanja uporabniških vprašanj, LLM lahko določi, kako poteka pogovor na podlagi opisa klasifikacije',
'start': 'Določanje začetnih parametrov za zagon poteka dela',
'if-else': 'Omogoča razdelitev poteka dela na dve veji glede na pogoje if/else',
'knowledge-retrieval': 'Omogoča poizvedovanje po besedilni vsebini, ki je povezana z uporabniškimi vprašanji iz zbirke znanja',
'variable-assigner': 'Združite spremenljivke z več vejami v eno spremenljivko za poenoteno konfiguracijo nadaljnjih vozlišč.',
'code': 'Izvedite kodo Python ali NodeJS za izvajanje logike po meri',
'answer': 'Določanje vsebine odgovora v pogovoru v klepetu',
'iteration': 'Izvedite več korakov na predmetu seznama, dokler niso prikazani vsi rezultati.',
'http-request': 'Dovoli pošiljanje zahtev strežnika prek protokola HTTP',
'end': 'Določanje končne in končne vrste poteka dela',
'variable-aggregator': 'Združite spremenljivke z več vejami v eno spremenljivko za poenoteno konfiguracijo nadaljnjih vozlišč.',
'parameter-extractor': 'Uporabite LLM za pridobivanje strukturiranih parametrov iz naravnega jezika za klicanje orodij ali zahteve HTTP.',
'assigner': 'Vozlišče za dodeljevanje spremenljivk se uporablja za dodeljevanje vrednosti zapisljivim spremenljivkam (kot so spremenljivke pogovora).',
'llm': 'Sklicevanje na velike jezikovne modele za odgovarjanje na vprašanja ali obdelavo naravnega jezika',
},
operator: {
zoomOut: 'Pomanjšanje',
zoomTo100: 'Povečava na 100 %',
zoomToFit: 'Povečaj, da se prilega',
zoomIn: 'Povečava',
zoomTo50: 'Povečava na 50%',
},
panel: {
helpLink: 'Povezava za pomoč',
organizeBlocks: 'Organiziranje blokov',
optional: '(neobvezno)',
nextStep: 'Naslednji korak',
checklist: 'Kontrolni seznam',
runThisStep: 'Zaženite ta korak',
about: 'Približno',
selectNextStep: 'Izberite Naslednji blok',
changeBlock: 'Spremeni blok',
createdBy: 'Ustvaril',
checklistTip: 'Pred objavo se prepričajte, da so vse težave odpravljene',
userInputField: 'Uporabniško polje za vnos',
checklistResolved: 'Vse težave so odpravljene',
addNextStep: 'Dodajanje naslednjega bloka v ta potek dela',
change: 'Spremeniti',
},
nodes: {
common: {
memory: {
conversationRoleName: 'Ime vloge pogovora',
memoryTip: 'Nastavitve pomnilnika klepeta',
assistant: 'Predpona pomočnika',
user: 'Uporabniška predpona',
memory: 'Spomin',
windowSize: 'Velikost okna',
},
memories: {
tip: 'Pomnilnik klepeta',
title: 'Spomine',
builtIn: 'Vgrajeno',
},
outputVars: 'Izhodne spremenljivke',
insertVarTip: 'Vstavi spremenljivko',
},
start: {
outputVars: {
memories: {
content: 'Vsebina sporočila',
des: 'Zgodovina pogovorov',
type: 'Vrsta sporočila',
},
query: 'Uporabniški vnos',
files: 'Seznam datotek',
},
required: 'Zahteva',
inputField: 'Vnosno polje',
noVarTip: 'Nastavitev vhodov, ki jih je mogoče uporabiti v poteku dela',
builtInVar: 'Vgrajene spremenljivke',
},
end: {
output: {
variable: 'izhodna spremenljivka',
type: 'Vrsta izhoda',
},
type: {
'structured': 'Strukturiran',
'plain-text': 'Navadno besedilo',
'none': 'Nobena',
},
outputs: 'Izhodov',
},
answer: {
answer: 'Odgovoriti',
outputVars: 'Izhodne spremenljivke',
},
llm: {
roleDescription: {
assistant: 'Odgovori modela na podlagi sporočil uporabnikov',
system: 'Podajte navodila na visoki ravni za pogovor',
user: 'Navedite navodila, poizvedbe ali kakršen koli besedilni vnos v model',
},
resolution: {
low: 'Nizek',
high: 'Visok',
name: 'Resolucija',
},
outputVars: {
usage: 'Informacije o uporabi modela',
output: 'Ustvarjanje vsebine',
},
singleRun: {
variable: 'Spremenljivka',
},
notSetContextInPromptTip: 'Če želite omogočiti funkcijo konteksta, izpolnite kontekstno spremenljivko v PROMPT.',
sysQueryInUser: 'sys.query v sporočilu uporabnika je obvezen',
model: 'model',
files: 'Datoteke',
addMessage: 'Dodaj sporočilo',
context: 'Kontekstu',
variables: 'Spremenljivke',
prompt: 'Uren',
vision: 'vid',
contextTooltip: 'Znanje lahko uvozite kot kontekst',
},
knowledgeRetrieval: {
outputVars: {
title: 'Segmentirani naslov',
url: 'Segmentirani URL',
output: 'Pridobivanje segmentiranih podatkov',
icon: 'Segmentirana ikona',
metadata: 'Drugi metapodatki',
content: 'Segmentirana vsebina',
},
queryVariable: 'Spremenljivka poizvedbe',
knowledge: 'Znanje',
},
http: {
outputVars: {
headers: 'JSON seznama glav odgovorov',
body: 'Vsebina odgovora',
files: 'Seznam datotek',
statusCode: 'Koda stanja odgovora',
},
authorization: {
'authorization': 'Dovoljenje',
'header': 'Glava',
'bearer': 'Nosilec',
'api-key-title': 'API ključ',
'basic': 'Osnoven',
'no-auth': 'Nobena',
'custom': 'Običaj',
'authorizationType': 'Vrsta dovoljenja',
'auth-type': 'Vrsta preverjanja pristnosti',
'api-key': 'Ključ API-ja',
},
timeout: {
readPlaceholder: 'Vnos časovne omejitve branja v sekundah',
writePlaceholder: 'Vnesite časovno omejitev pisanja v sekundah',
writeLabel: 'Časovna omejitev pisanja',
connectLabel: 'Časovna omejitev povezave',
title: 'Timeout',
readLabel: 'Časovna omejitev branja',
connectPlaceholder: 'Vnos časovne omejitve povezave v sekundah',
},
value: 'Vrednost',
key: 'Ključ',
notStartWithHttp: 'API se mora začeti z http:// ali https://',
body: 'Telo',
type: 'Vrsta',
inputVars: 'Vhodne spremenljivke',
bulkEdit: 'Urejanje v velikem obsegu',
insertVarPlaceholder: 'vnesite "/" za vstavljanje spremenljivke',
api: 'API',
keyValueEdit: 'Urejanje ključ-vrednost',
binaryFileVariable: 'Spremenljivka binarne datoteke',
headers: 'Glave',
apiPlaceholder: 'Vnesite URL, vnesite \'/\' vstavi spremenljivko',
extractListPlaceholder: 'Vnesite indeks elementa seznama, vnesite \'/\' vstavi spremenljivko',
params: 'Params',
},
code: {
inputVars: 'Vhodne spremenljivke',
outputVars: 'Izhodne spremenljivke',
searchDependencies: 'Odvisnosti iskanja',
advancedDependenciesTip: 'Tukaj dodajte nekaj vnaprej naloženih odvisnosti, ki trajajo dlje časa ali niso privzeto vgrajene',
advancedDependencies: 'Napredne odvisnosti',
},
templateTransform: {
outputVars: {
output: 'Preoblikovana vsebina',
},
code: 'Koda',
inputVars: 'Vhodne spremenljivke',
codeSupportTip: 'Podpira samo Jinja2',
},
ifElse: {
comparisonOperator: {
'all of': 'vse',
'is not': 'ni',
'not empty': 'ni prazen',
'start with': 'Začnite z',
'is': 'Je',
'null': 'je nična',
'not exists': 'ne obstaja',
'contains': 'Vsebuje',
'empty': 'je prazen',
'exists': 'Obstaja',
'in': 'v',
'not contains': 'ne vsebuje',
'end with': 'Končaj z',
'not in': 'ni v',
'not null': 'ni nična',
},
optionName: {
video: 'Video',
doc: 'Doc',
audio: 'Avdio',
image: 'Podoba',
url: 'Spletni naslov',
localUpload: 'Lokalno nalaganje',
},
and: 'in',
else: 'Drugega',
enterValue: 'Vnesite vrednost',
elseDescription: 'Uporablja se za določanje logike, ki jo je treba izvesti, ko pogoj if ni izpolnjen.',
addCondition: 'Dodajanje pogoja',
if: 'Če',
select: 'Izbrati',
selectVariable: 'Izberite spremenljivko ...',
conditionNotSetup: 'Pogoj NI nastavljen',
addSubVariable: 'Podspremenljivka',
notSetVariable: 'Prosimo, najprej nastavite spremenljivko',
operator: 'Operaterja',
or: 'ali',
},
variableAssigner: {
type: {
string: 'Niz',
object: 'Predmet',
array: 'Matrika',
number: 'Številka',
},
outputVars: {
varDescribe: '{{groupName}} izhod',
},
addGroup: 'Dodajanje skupine',
outputType: 'Vrsta izhoda',
title: 'Dodeljevanje spremenljivk',
noVarTip: 'Seštevanje spremenljivk, ki jih je treba dodeliti',
aggregationGroupTip: 'Če omogočite to funkcijo, lahko združevalnik spremenljivk združi več naborov spremenljivk.',
aggregationGroup: 'Združevalna skupina',
varNotSet: 'Spremenljivka ni nastavljena',
setAssignVariable: 'Nastavitev spremenljivke dodelitve',
},
assigner: {
'writeMode': 'Način pisanja',
'plus': 'Plus',
'variable': 'Spremenljivka',
'clear': 'Jasen',
'append': 'Dodaj',
'assignedVariable': 'Dodeljena spremenljivka',
'setVariable': 'Nastavi spremenljivko',
'over-write': 'Prepisati',
'writeModeTip': 'Način dodajanja: Na voljo samo za spremenljivke polja.',
},
tool: {
outputVars: {
files: {
transfer_method: 'Način prenosa. Vrednost je remote_url ali local_file',
upload_file_id: 'Naloži ID datoteke',
type: 'Vrsta podpore. Zdaj podpiramo samo sliko',
url: 'URL slike',
title: 'Datoteke, ustvarjene z orodjem',
},
json: 'JSON, ustvarjen z orodjem',
text: 'Vsebina, ustvarjena z orodjem',
},
inputVars: 'Vhodne spremenljivke',
toAuthorize: 'Za odobritev',
},
questionClassifiers: {
outputVars: {
className: 'Ime razreda',
},
instruction: 'Navodilo',
classNamePlaceholder: 'Napišite ime svojega razreda',
addClass: 'Dodajanje razreda',
instructionPlaceholder: 'Napišite navodila',
topicName: 'Ime teme',
topicPlaceholder: 'Napišite ime teme',
class: 'Razred',
advancedSetting: 'Napredne nastavitve',
model: 'model',
inputVars: 'Vhodne spremenljivke',
instructionTip: 'Vnesite dodatna navodila, ki bodo klasifikatorju vprašanj pomagala bolje razumeti, kako kategorizirati vprašanja.',
},
parameterExtractor: {
addExtractParameterContent: {
description: 'Opis',
typePlaceholder: 'Vrsta parametra izvlečka',
requiredContent: 'Zahtevano se uporablja samo kot referenca za sklepanje modela in ne za obvezno validacijo izhodnega parametra.',
required: 'Zahteva',
type: 'Vrsta',
namePlaceholder: 'Izvleček imena parametra',
descriptionPlaceholder: 'Opis parametra izvlečka',
name: 'Ime',
},
isSuccess: 'Je uspeh.Pri uspehu je vrednost 1, pri neuspehu je vrednost 0.',
addExtractParameter: 'Dodajanje parametra izvlečka',
importFromTool: 'Uvoz iz orodij',
reasoningModeTip: 'Izberete lahko ustrezen način sklepanja glede na sposobnost modela, da se odzove na navodila za klicanje funkcij ali pozive.',
inputVar: 'Vhodna spremenljivka',
advancedSetting: 'Napredne nastavitve',
errorReason: 'Razlog za napako',
reasoningMode: 'Način sklepanja',
instruction: 'Navodilo',
instructionTip: 'Vnesite dodatna navodila, ki bodo ekstraktorju parametrov pomagala razumeti, kako izvleči parametre.',
extractParametersNotSet: 'Izvleček parametrov ni nastavljen',
extractParameters: 'Izvleček parametrov',
},
iteration: {
ErrorMethod: {
continueOnError: 'Nadaljuj ob napaki',
removeAbnormalOutput: 'Odstranite nenormalen izhod',
operationTerminated: 'Prekinjena',
},
output: 'Izhodne spremenljivke',
parallelMode: 'Vzporedni način',
MaxParallelismTitle: 'Največji vzporednost',
errorResponseMethod: 'Način odziva na napako',
parallelModeEnableDesc: 'V vzporednem načinu opravila v iteracijah podpirajo vzporedno izvajanje. To lahko konfigurirate na plošči z lastnostmi na desni.',
error_one: '{{štetje}} Napaka',
comma: ',',
parallelModeUpper: 'VZPOREDNI NAČIN',
parallelModeEnableTitle: 'Vzporedni način omogočen',
currentIteration: 'Trenutna ponovitev',
error_other: '{{štetje}} Napake',
input: 'Vhodni',
deleteTitle: 'Izbrisati iteracijsko vozlišče?',
parallelPanelDesc: 'V vzporednem načinu opravila v iteraciji podpirajo vzporedno izvajanje.',
deleteDesc: 'Če izbrišete iteracijsko vozlišče, boste izbrisali vsa podrejena vozlišča',
iteration_other: '{{štetje}} Ponovitev',
answerNodeWarningDesc: 'Opozorilo vzporednega načina: Vozlišča za odgovore, dodelitve spremenljivk pogovora in trajne operacije branja / pisanja v iteracijah lahko povzročijo izjeme.',
MaxParallelismDesc: 'Največja vzporednost se uporablja za nadzor števila nalog, ki se izvajajo hkrati v eni ponovitvi.',
iteration_one: '{{štetje}} Ponovitev',
},
note: {
editor: {
medium: 'Srednja',
openLink: 'Odprt',
showAuthor: 'Pokaži avtorja',
bold: 'Smel',
strikethrough: 'Prečrtano',
large: 'Velik',
link: 'Povezava',
enterUrl: 'Vnesite URL ...',
small: 'Majhen',
italic: 'Ležeče',
invalidUrl: 'Neveljaven URL',
unlink: 'Prekini povezavo',
placeholder: 'Napišite svojo opombo ...',
bulletList: 'Seznam oznak',
},
addNote: 'Dodaj opombo',
},
docExtractor: {
outputVars: {
text: 'Izvlečeno besedilo',
},
inputVar: 'Vhodna spremenljivka',
learnMore: 'Izvedi več',
supportFileTypes: 'Podporne vrste datotek: {{types}}.',
},
listFilter: {
outputVars: {
result: 'Rezultat filtriranja',
first_record: 'Prvi zapis',
last_record: 'Zadnji zapis',
},
extractsCondition: 'Ekstrahiranje elementa N',
selectVariableKeyPlaceholder: 'Izberite ključ podspremenljivke',
asc: 'ASC',
orderBy: 'Naročite po',
filterCondition: 'Pogoj filtra',
filterConditionKey: 'Ključ pogoja filtra',
desc: 'DESC',
limit: 'Vrh N',
filterConditionComparisonOperator: 'Operator za primerjavo pogojev filtra',
inputVar: 'Vhodna spremenljivka',
filterConditionComparisonValue: 'Vrednost pogoja filtra',
},
},
}
export default translation

View File

@ -591,6 +591,10 @@ const translation = {
pasteFileLinkInvalid: 'Geçersiz dosya bağlantısı',
fileExtensionNotSupport: 'Dosya uzantısı desteklenmiyor',
},
license: {
expiring_plural: '{{count}} gün içinde sona eriyor',
expiring: 'Bir günde sona eriyor',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethodTip: 'Bir kimlik doğrulama yöntemi eklemek için lütfen sistem yöneticisine başvurun.',
sendVerificationCode: 'Doğrulama Kodu Gönder',
back: 'Geri',
licenseExpiredTip: 'Çalışma alanınız için Dify Enterprise lisansının süresi doldu. Dify\'ı kullanmaya devam etmek için lütfen yöneticinizle iletişime geçin.',
licenseLostTip: 'Dify lisans sunucusuna bağlanılamadı. Dify\'ı kullanmaya devam etmek için lütfen yöneticinizle iletişime geçin.',
licenseInactiveTip: 'Çalışma alanınız için Dify Enterprise lisansı etkin değil. Dify\'ı kullanmaya devam etmek için lütfen yöneticinizle iletişime geçin.',
licenseExpired: 'Lisansın Süresi Doldu',
licenseLost: 'Lisans Kaybedildi',
licenseInactive: 'Lisans Etkin Değil',
}
export default translation

View File

@ -408,6 +408,7 @@ const translation = {
},
type: 'Tür',
binaryFileVariable: 'İkili Dosya Değişkeni',
extractListPlaceholder: 'Liste öğesi dizinini girin, \'/\' yazın değişken ekle',
},
code: {
inputVars: 'Giriş Değişkenleri',
@ -619,6 +620,7 @@ const translation = {
filterConditionComparisonValue: 'Filtre Koşulu değeri',
selectVariableKeyPlaceholder: 'Alt değişken anahtarını seçin',
desc: 'DESC',
extractsCondition: 'N öğesini ayıklayın',
},
},
tracing: {

View File

@ -592,6 +592,10 @@ const translation = {
uploadFromComputerReadError: 'Не вдалося прочитати файл, будь ласка, спробуйте ще раз.',
uploadFromComputerUploadError: 'Не вдалося завантажити файл, будь ласка, завантажте ще раз.',
},
license: {
expiring: 'Термін дії закінчується за один день',
expiring_plural: 'Термін дії закінчується за {{count}} днів',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
noLoginMethodTip: 'Будь ласка, зверніться до адміністратора системи, щоб додати метод автентифікації.',
resetPasswordDesc: 'Введіть адресу електронної пошти, яку ви використовували для реєстрації на Dify, і ми надішлемо вам електронний лист для скидання пароля.',
resetPassword: 'Скинути пароль',
licenseLostTip: 'Не вдалося підключити сервер ліцензій Dify. Будь ласка, зверніться до свого адміністратора, щоб продовжити користуватися Dify.',
licenseExpired: 'Термін дії ліцензії минув',
licenseInactive: 'Ліцензія неактивна',
licenseLost: 'Ліцензію втрачено',
licenseInactiveTip: 'Ліцензія Dify Enterprise для вашої робочої області неактивна. Будь ласка, зверніться до свого адміністратора, щоб продовжити користуватися Dify.',
licenseExpiredTip: 'Термін дії ліцензії Dify Enterprise для вашого робочого простору закінчився. Будь ласка, зверніться до свого адміністратора, щоб продовжити користуватися Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: 'Тип',
binaryFileVariable: 'Змінна двійкового файлу',
extractListPlaceholder: 'Введіть індекс елемента списку, введіть \'/\' вставити змінну',
},
code: {
inputVars: 'Вхідні змінні',
@ -618,6 +619,7 @@ const translation = {
orderBy: 'Замовити по',
filterConditionComparisonOperator: 'Оператор порівняння умов фільтра',
filterConditionComparisonValue: 'Значення умови фільтра',
extractsCondition: 'Витягніть елемент N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
uploadFromComputerUploadError: 'Tải lên tệp không thành công, vui lòng tải lên lại.',
uploadFromComputerReadError: 'Đọc tệp không thành công, vui lòng thử lại.',
},
license: {
expiring_plural: 'Hết hạn sau {{count}} ngày',
expiring: 'Hết hạn trong một ngày',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
usePassword: 'Sử dụng mật khẩu',
resetPassword: 'Đặt lại mật khẩu',
sendVerificationCode: 'Gửi mã xác minh',
licenseInactive: 'Giấy phép không hoạt động',
licenseLost: 'Mất giấy phép',
licenseInactiveTip: 'Giấy phép Dify Enterprise cho không gian làm việc của bạn không hoạt động. Vui lòng liên hệ với quản trị viên của bạn để tiếp tục sử dụng Dify.',
licenseExpired: 'Giấy phép đã hết hạn',
licenseExpiredTip: 'Giấy phép Dify Enterprise cho không gian làm việc của bạn đã hết hạn. Vui lòng liên hệ với quản trị viên của bạn để tiếp tục sử dụng Dify.',
licenseLostTip: 'Không thể kết nối máy chủ cấp phép Dify. Vui lòng liên hệ với quản trị viên của bạn để tiếp tục sử dụng Dify.',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
binaryFileVariable: 'Biến tệp nhị phân',
type: 'Kiểu',
extractListPlaceholder: 'Nhập chỉ mục mục danh sách, nhập \'/\' chèn biến',
},
code: {
inputVars: 'Biến đầu vào',
@ -618,6 +619,7 @@ const translation = {
filterCondition: 'Điều kiện lọc',
asc: 'ASC',
filterConditionComparisonOperator: 'Toán tử so sánh điều kiện bộ lọc',
extractsCondition: 'Giải nén mục N',
},
},
tracing: {

View File

@ -591,6 +591,10 @@ const translation = {
fileExtensionNotSupport: '不支援檔擴展名',
uploadFromComputerLimit: '上傳文件不能超過 {{size}}',
},
license: {
expiring: '將在1天內過期',
expiring_plural: '將在 {{count}} 天后過期',
},
}
export default translation

View File

@ -99,6 +99,12 @@ const translation = {
back: '返回',
resetPasswordDesc: '輸入您用於註冊 Dify 的電子郵件,我們將向您發送一封密碼重置電子郵件。',
usePassword: '使用密碼',
licenseExpiredTip: '您的工作區的 Dify Enterprise 許可證已過期。請聯繫您的管理員以繼續使用 Dify。',
licenseExpired: '許可證已過期',
licenseLost: '許可證丟失',
licenseInactive: '許可證處於非活動狀態',
licenseInactiveTip: '您的工作區的 Dify Enterprise 許可證處於非活動狀態。請聯繫您的管理員以繼續使用 Dify。',
licenseLostTip: '無法連接 Dify 許可證伺服器。請聯繫您的管理員以繼續使用 Dify。',
}
export default translation

View File

@ -407,6 +407,7 @@ const translation = {
},
type: '類型',
binaryFileVariable: '二進位檔變數',
extractListPlaceholder: '輸入清單項索引,鍵入 『/』 插入變數',
},
code: {
inputVars: '輸入變量',
@ -618,6 +619,7 @@ const translation = {
selectVariableKeyPlaceholder: 'Select sub variable key (選擇子變數鍵)',
filterConditionComparisonOperator: 'Filter Condition Comparison 運算符',
filterConditionKey: '篩選條件鍵',
extractsCondition: '提取第 N 項',
},
},
tracing: {