mirror of https://github.com/langgenius/dify.git
Merge 26aa02e452 into 09be869f58
This commit is contained in:
commit
e1b27e2cf9
|
|
@ -9,10 +9,11 @@ from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from core.agent.entities import AgentToolEntity
|
from core.agent.entities import AgentToolEntity
|
||||||
from core.agent.plugin_entities import AgentStrategyParameter
|
from core.agent.plugin_entities import AgentStrategyParameter
|
||||||
from core.file import File, FileTransferMethod
|
from core.file import File, FileTransferMethod, FileType, file_manager
|
||||||
from core.memory.token_buffer_memory import TokenBufferMemory
|
from core.memory.token_buffer_memory import TokenBufferMemory
|
||||||
from core.model_manager import ModelInstance, ModelManager
|
from core.model_manager import ModelInstance, ModelManager
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage, LLMUsageMetadata
|
from core.model_runtime.entities.llm_entities import LLMUsage, LLMUsageMetadata
|
||||||
|
from core.model_runtime.entities.message_entities import TextPromptMessageContent
|
||||||
from core.model_runtime.entities.model_entities import AIModelEntity, ModelType
|
from core.model_runtime.entities.model_entities import AIModelEntity, ModelType
|
||||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||||
from core.provider_manager import ProviderManager
|
from core.provider_manager import ProviderManager
|
||||||
|
|
@ -24,7 +25,7 @@ from core.tools.entities.tool_entities import (
|
||||||
)
|
)
|
||||||
from core.tools.tool_manager import ToolManager
|
from core.tools.tool_manager import ToolManager
|
||||||
from core.tools.utils.message_transformer import ToolFileMessageTransformer
|
from core.tools.utils.message_transformer import ToolFileMessageTransformer
|
||||||
from core.variables.segments import ArrayFileSegment, StringSegment
|
from core.variables.segments import ArrayFileSegment, FileSegment, StringSegment
|
||||||
from core.workflow.enums import (
|
from core.workflow.enums import (
|
||||||
NodeType,
|
NodeType,
|
||||||
SystemVariableKey,
|
SystemVariableKey,
|
||||||
|
|
@ -160,6 +161,22 @@ class AgentNode(Node[AgentNodeData]):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _fetch_files_from_variable_selector(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
variable_pool: VariablePool,
|
||||||
|
selector: Sequence[str],
|
||||||
|
) -> Sequence[File]:
|
||||||
|
"""Fetch files from a variable selector."""
|
||||||
|
variable = variable_pool.get(list(selector))
|
||||||
|
if variable is None:
|
||||||
|
return []
|
||||||
|
elif isinstance(variable, FileSegment):
|
||||||
|
return [variable.value]
|
||||||
|
elif isinstance(variable, ArrayFileSegment):
|
||||||
|
return variable.value
|
||||||
|
return []
|
||||||
|
|
||||||
def _generate_agent_parameters(
|
def _generate_agent_parameters(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|
@ -206,11 +223,52 @@ class AgentNode(Node[AgentNodeData]):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
parameter_value = str(agent_input.value)
|
parameter_value = str(agent_input.value)
|
||||||
segment_group = variable_pool.convert_template(parameter_value)
|
segment_group = variable_pool.convert_template(parameter_value)
|
||||||
|
|
||||||
|
if parameter_name in ("query", "instruction") and not for_log:
|
||||||
|
contents: list[dict[str, Any]] = []
|
||||||
|
has_file = False
|
||||||
|
vision_detail = node_data.vision.configs.detail if node_data.vision.enabled else None
|
||||||
|
|
||||||
|
for segment in segment_group.value:
|
||||||
|
if isinstance(segment, (ArrayFileSegment, FileSegment)):
|
||||||
|
files = segment.value if isinstance(segment, ArrayFileSegment) else [segment.value]
|
||||||
|
for file in files:
|
||||||
|
if file.type in {FileType.IMAGE, FileType.VIDEO, FileType.AUDIO, FileType.DOCUMENT}:
|
||||||
|
file_content = file_manager.to_prompt_message_content(
|
||||||
|
file, image_detail_config=vision_detail
|
||||||
|
)
|
||||||
|
contents.append(file_content.model_dump())
|
||||||
|
has_file = True
|
||||||
|
else:
|
||||||
|
text = segment.text
|
||||||
|
if text:
|
||||||
|
contents.append(TextPromptMessageContent(data=text).model_dump())
|
||||||
|
|
||||||
|
if parameter_name == "query":
|
||||||
|
if node_data.vision.enabled and node_data.vision.configs.variable_selector:
|
||||||
|
vision_files = self._fetch_files_from_variable_selector(
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
selector=node_data.vision.configs.variable_selector,
|
||||||
|
)
|
||||||
|
for file in vision_files:
|
||||||
|
if file.type in {FileType.IMAGE, FileType.VIDEO, FileType.AUDIO, FileType.DOCUMENT}:
|
||||||
|
file_content = file_manager.to_prompt_message_content(
|
||||||
|
file, image_detail_config=vision_detail
|
||||||
|
)
|
||||||
|
contents.append(file_content.model_dump())
|
||||||
|
has_file = True
|
||||||
|
|
||||||
|
if has_file:
|
||||||
|
parameter_value = contents
|
||||||
|
else:
|
||||||
|
parameter_value = segment_group.text
|
||||||
|
else:
|
||||||
parameter_value = segment_group.log if for_log else segment_group.text
|
parameter_value = segment_group.log if for_log else segment_group.text
|
||||||
|
|
||||||
# variable_pool.convert_template returns a string,
|
# variable_pool.convert_template returns a string,
|
||||||
# so we need to convert it back to a dictionary
|
# so we need to convert it back to a dictionary
|
||||||
try:
|
try:
|
||||||
if not isinstance(agent_input.value, str):
|
if not isinstance(agent_input.value, str) and isinstance(parameter_value, str):
|
||||||
parameter_value = json.loads(parameter_value)
|
parameter_value = json.loads(parameter_value)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
parameter_value = parameter_value
|
parameter_value = parameter_value
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,38 @@
|
||||||
|
from collections.abc import Sequence
|
||||||
from enum import IntEnum, StrEnum, auto
|
from enum import IntEnum, StrEnum, auto
|
||||||
from typing import Any, Literal, Union
|
from typing import Any, Literal, Union
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
from core.model_runtime.entities import ImagePromptMessageContent
|
||||||
from core.prompt.entities.advanced_prompt_entities import MemoryConfig
|
from core.prompt.entities.advanced_prompt_entities import MemoryConfig
|
||||||
from core.tools.entities.tool_entities import ToolSelector
|
from core.tools.entities.tool_entities import ToolSelector
|
||||||
from core.workflow.nodes.base.entities import BaseNodeData
|
from core.workflow.nodes.base.entities import BaseNodeData
|
||||||
|
|
||||||
|
|
||||||
|
class VisionConfigOptions(BaseModel):
|
||||||
|
variable_selector: Sequence[str] = Field(default_factory=lambda: ["sys", "files"])
|
||||||
|
detail: ImagePromptMessageContent.DETAIL = ImagePromptMessageContent.DETAIL.HIGH
|
||||||
|
|
||||||
|
|
||||||
|
class VisionConfig(BaseModel):
|
||||||
|
enabled: bool = False
|
||||||
|
configs: VisionConfigOptions = Field(default_factory=VisionConfigOptions)
|
||||||
|
|
||||||
|
@field_validator("configs", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def convert_none_configs(cls, v: Any):
|
||||||
|
if v is None:
|
||||||
|
return VisionConfigOptions()
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class AgentNodeData(BaseNodeData):
|
class AgentNodeData(BaseNodeData):
|
||||||
agent_strategy_provider_name: str # redundancy
|
agent_strategy_provider_name: str # redundancy
|
||||||
agent_strategy_name: str
|
agent_strategy_name: str
|
||||||
agent_strategy_label: str # redundancy
|
agent_strategy_label: str # redundancy
|
||||||
memory: MemoryConfig | None = None
|
memory: MemoryConfig | None = None
|
||||||
|
vision: VisionConfig = Field(default_factory=VisionConfig)
|
||||||
# The version of the tool parameter.
|
# The version of the tool parameter.
|
||||||
# If this value is None, it indicates this is a previous version
|
# If this value is None, it indicates this is a previous version
|
||||||
# and requires using the legacy parameter parsing rules.
|
# and requires using the legacy parameter parsing rules.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,467 @@
|
||||||
|
"""Unit tests for AgentNode file handling."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.workflow.nodes.agent.agent_node import AgentNode
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_stubs(monkeypatch):
|
||||||
|
"""Set up stubs for circular import issues."""
|
||||||
|
module_name = "core.ops.ops_trace_manager"
|
||||||
|
if module_name not in sys.modules:
|
||||||
|
ops_stub = types.ModuleType(module_name)
|
||||||
|
ops_stub.TraceQueueManager = object
|
||||||
|
ops_stub.TraceTask = object
|
||||||
|
monkeypatch.setitem(sys.modules, module_name, ops_stub)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def agent_imports(monkeypatch) -> dict[str, Any]:
|
||||||
|
"""Set up stubs and return imported modules."""
|
||||||
|
_setup_stubs(monkeypatch)
|
||||||
|
|
||||||
|
from core.agent.plugin_entities import AgentStrategyParameter
|
||||||
|
from core.file import File, FileTransferMethod, FileType, file_manager
|
||||||
|
from core.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||||
|
from core.variables import FileSegment, StringSegment
|
||||||
|
from core.workflow.nodes.agent.agent_node import AgentNode
|
||||||
|
from core.workflow.nodes.agent.entities import AgentNodeData, VisionConfig, VisionConfigOptions
|
||||||
|
from core.workflow.runtime.variable_pool import VariablePool
|
||||||
|
|
||||||
|
return {
|
||||||
|
"AgentNode": AgentNode,
|
||||||
|
"AgentNodeData": AgentNodeData,
|
||||||
|
"VisionConfig": VisionConfig,
|
||||||
|
"VisionConfigOptions": VisionConfigOptions,
|
||||||
|
"VariablePool": VariablePool,
|
||||||
|
"AgentStrategyParameter": AgentStrategyParameter,
|
||||||
|
"File": File,
|
||||||
|
"FileTransferMethod": FileTransferMethod,
|
||||||
|
"FileType": FileType,
|
||||||
|
"file_manager": file_manager,
|
||||||
|
"ImagePromptMessageContent": ImagePromptMessageContent,
|
||||||
|
"FileSegment": FileSegment,
|
||||||
|
"StringSegment": StringSegment,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgentNodeFileHandling:
|
||||||
|
"""Tests for file handling in query, instruction, and vision variable selector."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_file(self, agent_imports):
|
||||||
|
"""Create a mock file."""
|
||||||
|
File = agent_imports["File"]
|
||||||
|
FileType = agent_imports["FileType"]
|
||||||
|
FileTransferMethod = agent_imports["FileTransferMethod"]
|
||||||
|
return File(
|
||||||
|
id="test-file-id",
|
||||||
|
tenant_id="test-tenant",
|
||||||
|
type=FileType.IMAGE,
|
||||||
|
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||||
|
related_id="test-related-id",
|
||||||
|
filename="test.png",
|
||||||
|
extension=".png",
|
||||||
|
mime_type="image/png",
|
||||||
|
size=1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_custom_file(self, agent_imports):
|
||||||
|
"""Create a mock custom (unsupported) file."""
|
||||||
|
File = agent_imports["File"]
|
||||||
|
FileType = agent_imports["FileType"]
|
||||||
|
FileTransferMethod = agent_imports["FileTransferMethod"]
|
||||||
|
return File(
|
||||||
|
id="test-custom-id",
|
||||||
|
tenant_id="test-tenant",
|
||||||
|
type=FileType.CUSTOM,
|
||||||
|
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||||
|
related_id="test-related-id",
|
||||||
|
filename="test.zip",
|
||||||
|
extension=".zip",
|
||||||
|
mime_type="application/zip",
|
||||||
|
size=4096,
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_strategy(self, agent_imports) -> MagicMock:
|
||||||
|
"""Create a mock agent strategy."""
|
||||||
|
AgentStrategyParameter = agent_imports["AgentStrategyParameter"]
|
||||||
|
strategy = MagicMock()
|
||||||
|
strategy.get_parameters.return_value = [
|
||||||
|
AgentStrategyParameter(
|
||||||
|
name="query",
|
||||||
|
type=AgentStrategyParameter.AgentStrategyParameterType.STRING,
|
||||||
|
required=True,
|
||||||
|
label={"en_US": "Query"},
|
||||||
|
),
|
||||||
|
AgentStrategyParameter(
|
||||||
|
name="instruction",
|
||||||
|
type=AgentStrategyParameter.AgentStrategyParameterType.STRING,
|
||||||
|
required=False,
|
||||||
|
label={"en_US": "Instruction"},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return strategy
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def base_node_data(self, agent_imports) -> dict:
|
||||||
|
"""Create base node data for tests."""
|
||||||
|
VisionConfig = agent_imports["VisionConfig"]
|
||||||
|
return {
|
||||||
|
"title": "Test Agent",
|
||||||
|
"agent_strategy_provider_name": "test-provider",
|
||||||
|
"agent_strategy_name": "test-strategy",
|
||||||
|
"agent_strategy_label": "Test Strategy",
|
||||||
|
"agent_parameters": {},
|
||||||
|
"vision": VisionConfig(enabled=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_agent_node(self, agent_imports) -> "AgentNode":
|
||||||
|
"""Create an AgentNode instance for testing."""
|
||||||
|
AgentNode = agent_imports["AgentNode"]
|
||||||
|
node = object.__new__(AgentNode)
|
||||||
|
node.tenant_id = "test-tenant"
|
||||||
|
node.app_id = "test-app"
|
||||||
|
return node
|
||||||
|
|
||||||
|
def test_query_with_text_only_returns_string(self, agent_imports, mock_strategy, base_node_data):
|
||||||
|
"""When query contains only text, it should return a string."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
StringSegment = agent_imports["StringSegment"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "text_var"], StringSegment(value="Hello, world!"))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.text_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert result["query"] == "Hello, world!"
|
||||||
|
assert isinstance(result["query"], str)
|
||||||
|
|
||||||
|
def test_query_with_file_returns_list(self, agent_imports, mock_file, mock_strategy, base_node_data):
|
||||||
|
"""When query contains a file, it should return a list."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
file_manager = agent_imports["file_manager"]
|
||||||
|
ImagePromptMessageContent = agent_imports["ImagePromptMessageContent"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
with patch.object(file_manager, "to_prompt_message_content") as mock_to_content:
|
||||||
|
mock_to_content.return_value = ImagePromptMessageContent(
|
||||||
|
url="http://example.com/test.png", mime_type="image/png", format="png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(result["query"], list)
|
||||||
|
assert len(result["query"]) == 1
|
||||||
|
assert result["query"][0]["type"] == "image"
|
||||||
|
|
||||||
|
def test_query_with_text_and_file_returns_list_with_both(
|
||||||
|
self, agent_imports, mock_file, mock_strategy, base_node_data
|
||||||
|
):
|
||||||
|
"""When query contains both text and file, it should return a list with both."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
file_manager = agent_imports["file_manager"]
|
||||||
|
ImagePromptMessageContent = agent_imports["ImagePromptMessageContent"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="Describe this: {{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
with patch.object(file_manager, "to_prompt_message_content") as mock_to_content:
|
||||||
|
mock_to_content.return_value = ImagePromptMessageContent(
|
||||||
|
url="http://example.com/test.png", mime_type="image/png", format="png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(result["query"], list)
|
||||||
|
assert len(result["query"]) == 2
|
||||||
|
assert result["query"][0]["type"] == "text"
|
||||||
|
assert result["query"][1]["type"] == "image"
|
||||||
|
|
||||||
|
def test_custom_file_type_is_ignored(self, agent_imports, mock_custom_file, mock_strategy, base_node_data):
|
||||||
|
"""Custom file types should be ignored and return text only."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_custom_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# Custom file types are ignored, so result should be the text representation
|
||||||
|
assert isinstance(result["query"], str)
|
||||||
|
|
||||||
|
def test_instruction_with_file_returns_list(self, agent_imports, mock_file, mock_strategy, base_node_data):
|
||||||
|
"""When instruction contains a file, it should return a list (same as query)."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
file_manager = agent_imports["file_manager"]
|
||||||
|
ImagePromptMessageContent = agent_imports["ImagePromptMessageContent"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"instruction": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="You are a helpful assistant. {{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
with patch.object(file_manager, "to_prompt_message_content") as mock_to_content:
|
||||||
|
mock_to_content.return_value = ImagePromptMessageContent(
|
||||||
|
url="http://example.com/test.png", mime_type="image/png", format="png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(result["instruction"], list)
|
||||||
|
assert len(result["instruction"]) == 2
|
||||||
|
assert result["instruction"][0]["type"] == "text"
|
||||||
|
assert result["instruction"][1]["type"] == "image"
|
||||||
|
|
||||||
|
def test_vision_variable_selector_files_added_to_query(
|
||||||
|
self, agent_imports, mock_file, mock_strategy, base_node_data
|
||||||
|
):
|
||||||
|
"""Vision variable selector files should be added to query only."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
StringSegment = agent_imports["StringSegment"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
VisionConfig = agent_imports["VisionConfig"]
|
||||||
|
VisionConfigOptions = agent_imports["VisionConfigOptions"]
|
||||||
|
file_manager = agent_imports["file_manager"]
|
||||||
|
ImagePromptMessageContent = agent_imports["ImagePromptMessageContent"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "text_var"], StringSegment(value="Describe this image"))
|
||||||
|
variable_pool.add(["sys", "files"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.text_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
base_node_data["vision"] = VisionConfig(
|
||||||
|
enabled=True,
|
||||||
|
configs=VisionConfigOptions(
|
||||||
|
variable_selector=["sys", "files"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
with patch.object(file_manager, "to_prompt_message_content") as mock_to_content:
|
||||||
|
mock_to_content.return_value = ImagePromptMessageContent(
|
||||||
|
url="http://example.com/test.png", mime_type="image/png", format="png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(result["query"], list)
|
||||||
|
assert len(result["query"]) == 2
|
||||||
|
assert result["query"][0]["type"] == "text"
|
||||||
|
assert result["query"][0]["data"] == "Describe this image"
|
||||||
|
assert result["query"][1]["type"] == "image"
|
||||||
|
|
||||||
|
def test_for_log_returns_text_representation(self, agent_imports, mock_file, mock_strategy, base_node_data):
|
||||||
|
"""When for_log is True, files should be represented as log text."""
|
||||||
|
# Arrange
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"query": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=True,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# for_log=True should return log representation, not a list
|
||||||
|
assert isinstance(result["query"], str)
|
||||||
|
|
||||||
|
def test_non_query_instruction_parameter_returns_text(
|
||||||
|
self, agent_imports, mock_file, mock_strategy, base_node_data
|
||||||
|
):
|
||||||
|
"""Parameters other than query/instruction should return text even with files."""
|
||||||
|
# Arrange
|
||||||
|
AgentStrategyParameter = agent_imports["AgentStrategyParameter"]
|
||||||
|
VariablePool = agent_imports["VariablePool"]
|
||||||
|
AgentNodeData = agent_imports["AgentNodeData"]
|
||||||
|
FileSegment = agent_imports["FileSegment"]
|
||||||
|
|
||||||
|
mock_strategy.get_parameters.return_value = [
|
||||||
|
AgentStrategyParameter(
|
||||||
|
name="other_param",
|
||||||
|
type=AgentStrategyParameter.AgentStrategyParameterType.STRING,
|
||||||
|
required=False,
|
||||||
|
label={"en_US": "Other"},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(["node1", "file_var"], FileSegment(value=mock_file))
|
||||||
|
|
||||||
|
base_node_data["agent_parameters"] = {
|
||||||
|
"other_param": AgentNodeData.AgentInput(
|
||||||
|
type="mixed",
|
||||||
|
value="{{#node1.file_var#}}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
node_data = AgentNodeData.model_validate(base_node_data)
|
||||||
|
agent_parameters = mock_strategy.get_parameters()
|
||||||
|
|
||||||
|
agent_node = self._create_agent_node(agent_imports)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = agent_node._generate_agent_parameters(
|
||||||
|
agent_parameters=agent_parameters,
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
node_data=node_data,
|
||||||
|
for_log=False,
|
||||||
|
strategy=mock_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# Non-query/instruction parameters should return text representation
|
||||||
|
assert isinstance(result["other_param"], str)
|
||||||
|
|
@ -6,8 +6,10 @@ import type { StrategyParamItem } from '@/app/components/plugins/types'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { toType } from '@/app/components/tools/utils/to-form-schema'
|
import { toType } from '@/app/components/tools/utils/to-form-schema'
|
||||||
|
import { Resolution } from '@/types/app'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import { AgentStrategy } from '../_base/components/agent-strategy'
|
import { AgentStrategy } from '../_base/components/agent-strategy'
|
||||||
|
import ConfigVision from '../_base/components/config-vision'
|
||||||
import Field from '../_base/components/field'
|
import Field from '../_base/components/field'
|
||||||
import MemoryConfig from '../_base/components/memory-config'
|
import MemoryConfig from '../_base/components/memory-config'
|
||||||
import OutputVars, { VarItem } from '../_base/components/output-vars'
|
import OutputVars, { VarItem } from '../_base/components/output-vars'
|
||||||
|
|
@ -40,6 +42,8 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||||
readOnly,
|
readOnly,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
|
handleVisionEnabledChange,
|
||||||
|
handleVisionConfigChange,
|
||||||
canChooseMCPTool,
|
canChooseMCPTool,
|
||||||
} = useConfig(props.id, props.data)
|
} = useConfig(props.id, props.data)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -85,12 +89,11 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||||
canChooseMCPTool={canChooseMCPTool}
|
canChooseMCPTool={canChooseMCPTool}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<div className="px-4 py-2">
|
<div className="space-y-4 px-4 py-2">
|
||||||
{isChatMode && currentStrategy?.features?.includes(AgentFeature.HISTORY_MESSAGES) && (
|
{isChatMode && currentStrategy?.features?.includes(AgentFeature.HISTORY_MESSAGES) && (
|
||||||
<>
|
<>
|
||||||
<Split />
|
<Split />
|
||||||
<MemoryConfig
|
<MemoryConfig
|
||||||
className="mt-4"
|
|
||||||
readonly={readOnly}
|
readonly={readOnly}
|
||||||
config={{ data: inputs.memory }}
|
config={{ data: inputs.memory }}
|
||||||
onChange={handleMemoryChange}
|
onChange={handleMemoryChange}
|
||||||
|
|
@ -98,6 +101,15 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<ConfigVision
|
||||||
|
nodeId={props.id}
|
||||||
|
readOnly={readOnly}
|
||||||
|
isVisionModel={true}
|
||||||
|
enabled={inputs.vision?.enabled || false}
|
||||||
|
onEnabledChange={handleVisionEnabledChange}
|
||||||
|
config={inputs.vision?.configs || { detail: Resolution.high, variable_selector: [] }}
|
||||||
|
onConfigChange={handleVisionConfigChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import type { ToolVarInputs } from '../tool/types'
|
import type { ToolVarInputs } from '../tool/types'
|
||||||
import type { PluginMeta } from '@/app/components/plugins/types'
|
import type { PluginMeta } from '@/app/components/plugins/types'
|
||||||
import type { CommonNodeType, Memory } from '@/app/components/workflow/types'
|
import type { CommonNodeType, Memory, VisionSetting } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
export type AgentVisionConfig = {
|
||||||
|
enabled: boolean
|
||||||
|
configs?: VisionSetting
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentNodeType = CommonNodeType & {
|
export type AgentNodeType = CommonNodeType & {
|
||||||
agent_strategy_provider_name?: string
|
agent_strategy_provider_name?: string
|
||||||
|
|
@ -11,6 +16,7 @@ export type AgentNodeType = CommonNodeType & {
|
||||||
output_schema: Record<string, any>
|
output_schema: Record<string, any>
|
||||||
plugin_unique_identifier?: string
|
plugin_unique_identifier?: string
|
||||||
memory?: Memory
|
memory?: Memory
|
||||||
|
vision?: AgentVisionConfig
|
||||||
version?: string
|
version?: string
|
||||||
tool_node_version?: string
|
tool_node_version?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Memory, Var } from '../../types'
|
import type { Memory, Var, VisionSetting } from '../../types'
|
||||||
import type { ToolVarInputs } from '../tool/types'
|
import type { ToolVarInputs } from '../tool/types'
|
||||||
import type { AgentNodeType } from './types'
|
import type { AgentNodeType } from './types'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins'
|
import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins'
|
||||||
import { useStrategyProviderDetail } from '@/service/use-strategy'
|
import { useStrategyProviderDetail } from '@/service/use-strategy'
|
||||||
|
import { Resolution } from '@/types/app'
|
||||||
import { isSupportMCP } from '@/utils/plugin-version-feature'
|
import { isSupportMCP } from '@/utils/plugin-version-feature'
|
||||||
import { VarType as VarKindType } from '../../types'
|
import { VarType as VarKindType } from '../../types'
|
||||||
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
|
|
@ -204,7 +205,34 @@ const useConfig = (id: string, payload: AgentNodeType) => {
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
|
|
||||||
|
const handleVisionEnabledChange = useCallback((enabled: boolean) => {
|
||||||
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
if (!draft.vision) {
|
||||||
|
draft.vision = { enabled: false }
|
||||||
|
}
|
||||||
|
draft.vision.enabled = enabled
|
||||||
|
if (enabled && isChatMode) {
|
||||||
|
draft.vision.configs = {
|
||||||
|
detail: Resolution.high,
|
||||||
|
variable_selector: ['sys', 'files'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setInputs(newInputs)
|
||||||
|
}, [inputs, setInputs, isChatMode])
|
||||||
|
|
||||||
|
const handleVisionConfigChange = useCallback((config: VisionSetting) => {
|
||||||
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
if (!draft.vision) {
|
||||||
|
draft.vision = { enabled: true }
|
||||||
|
}
|
||||||
|
draft.vision.configs = config
|
||||||
|
})
|
||||||
|
setInputs(newInputs)
|
||||||
|
}, [inputs, setInputs])
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
|
|
@ -221,6 +249,8 @@ const useConfig = (id: string, payload: AgentNodeType) => {
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
|
handleVisionEnabledChange,
|
||||||
|
handleVisionConfigChange,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
canChooseMCPTool: isSupportMCP(inputs.meta?.version),
|
canChooseMCPTool: isSupportMCP(inputs.meta?.version),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1024,6 +1024,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'تم إهمال هذا النموذج',
|
deprecated: 'تم إهمال هذا النموذج',
|
||||||
},
|
},
|
||||||
|
vision: 'الرؤية',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
text: 'محتوى تم إنشاؤه بواسطة الوكيل',
|
text: 'محتوى تم إنشاؤه بواسطة الوكيل',
|
||||||
usage: 'معلومات استخدام النموذج',
|
usage: 'معلومات استخدام النموذج',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Dieses Modell ist veraltet',
|
deprecated: 'Dieses Modell ist veraltet',
|
||||||
},
|
},
|
||||||
|
vision: 'Vision',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
type: 'Art der Unterstützung. Jetzt nur noch Image unterstützen',
|
type: 'Art der Unterstützung. Jetzt nur noch Image unterstützen',
|
||||||
|
|
|
||||||
|
|
@ -1024,6 +1024,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'This model is deprecated',
|
deprecated: 'This model is deprecated',
|
||||||
},
|
},
|
||||||
|
vision: 'vision',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
text: 'agent generated content',
|
text: 'agent generated content',
|
||||||
usage: 'Model Usage Information',
|
usage: 'Model Usage Information',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Este modelo está en desuso',
|
deprecated: 'Este modelo está en desuso',
|
||||||
},
|
},
|
||||||
|
vision: 'visión',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
url: 'URL de la imagen',
|
url: 'URL de la imagen',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'این مدل منسوخ شده است',
|
deprecated: 'این مدل منسوخ شده است',
|
||||||
},
|
},
|
||||||
|
vision: 'بینایی',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
transfer_method: 'روش انتقال. ارزش remote_url یا local_file',
|
transfer_method: 'روش انتقال. ارزش remote_url یا local_file',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Ce modèle est obsolète',
|
deprecated: 'Ce modèle est obsolète',
|
||||||
},
|
},
|
||||||
|
vision: 'vision',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
title: 'Fichiers générés par l’agent',
|
title: 'Fichiers générés par l’agent',
|
||||||
|
|
|
||||||
|
|
@ -921,6 +921,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'यह मॉडल अप्रचलित है।',
|
deprecated: 'यह मॉडल अप्रचलित है।',
|
||||||
},
|
},
|
||||||
|
vision: 'दृष्टि',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
transfer_method: 'स्थानांतरण विधि। मान या तो remote_url है या local_file।',
|
transfer_method: 'स्थानांतरण विधि। मान या तो remote_url है या local_file।',
|
||||||
|
|
|
||||||
|
|
@ -940,6 +940,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Model ini tidak digunakan lagi',
|
deprecated: 'Model ini tidak digunakan lagi',
|
||||||
},
|
},
|
||||||
|
vision: 'penglihatan',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
transfer_method: 'Metode transfer. Nilai adalah remote_url atau local_file',
|
transfer_method: 'Metode transfer. Nilai adalah remote_url atau local_file',
|
||||||
|
|
|
||||||
|
|
@ -927,6 +927,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Questo modello è deprecato',
|
deprecated: 'Questo modello è deprecato',
|
||||||
},
|
},
|
||||||
|
vision: 'vision',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
type: 'Tipo di supporto. Ora supporta solo l\'immagine',
|
type: 'Tipo di supporto. Ora supporta solo l\'immagine',
|
||||||
|
|
|
||||||
|
|
@ -960,6 +960,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'このモデルは廃止されました',
|
deprecated: 'このモデルは廃止されました',
|
||||||
},
|
},
|
||||||
|
vision: 'ビジョン',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
url: '画像の URL',
|
url: '画像の URL',
|
||||||
|
|
|
||||||
|
|
@ -943,6 +943,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: '이 모델은 더 이상 사용되지 않습니다.',
|
deprecated: '이 모델은 더 이상 사용되지 않습니다.',
|
||||||
},
|
},
|
||||||
|
vision: '비전',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
url: '이미지 URL',
|
url: '이미지 URL',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Ten model jest przestarzały',
|
deprecated: 'Ten model jest przestarzały',
|
||||||
},
|
},
|
||||||
|
vision: 'wizja',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
title: 'Pliki generowane przez agenta',
|
title: 'Pliki generowane przez agenta',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Este modelo está obsoleto',
|
deprecated: 'Este modelo está obsoleto',
|
||||||
},
|
},
|
||||||
|
vision: 'visão',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
type: 'Tipo de suporte. Agora suporta apenas imagem',
|
type: 'Tipo de suporte. Agora suporta apenas imagem',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Acest model este învechit',
|
deprecated: 'Acest model este învechit',
|
||||||
},
|
},
|
||||||
|
vision: 'viziune',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
upload_file_id: 'Încărcați ID-ul fișierului',
|
upload_file_id: 'Încărcați ID-ul fișierului',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Эта модель устарела',
|
deprecated: 'Эта модель устарела',
|
||||||
},
|
},
|
||||||
|
vision: 'зрение',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
transfer_method: 'Способ переноса. Ценность составляет remote_url или local_file',
|
transfer_method: 'Способ переноса. Ценность составляет remote_url или local_file',
|
||||||
|
|
|
||||||
|
|
@ -940,6 +940,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Ta model je zastarelo',
|
deprecated: 'Ta model je zastarelo',
|
||||||
},
|
},
|
||||||
|
vision: 'vizija',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
type: 'Vrsta podpore. Zdaj podpiramo samo slike.',
|
type: 'Vrsta podpore. Zdaj podpiramo samo slike.',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'โมเดลนี้เลิกใช้แล้ว',
|
deprecated: 'โมเดลนี้เลิกใช้แล้ว',
|
||||||
},
|
},
|
||||||
|
vision: 'การมองเห็น',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
transfer_method: 'วิธีการโอน ค่าเป็น remote_url หรือ local_file',
|
transfer_method: 'วิธีการโอน ค่าเป็น remote_url หรือ local_file',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Bu model kullanım dışıdır',
|
deprecated: 'Bu model kullanım dışıdır',
|
||||||
},
|
},
|
||||||
|
vision: 'görsel',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
upload_file_id: 'Dosya kimliğini karşıya yükle',
|
upload_file_id: 'Dosya kimliğini karşıya yükle',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Ця модель вважається застарілою',
|
deprecated: 'Ця модель вважається застарілою',
|
||||||
},
|
},
|
||||||
|
vision: 'бачення',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
upload_file_id: 'Завантажити ідентифікатор файлу',
|
upload_file_id: 'Завантажити ідентифікатор файлу',
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: 'Mô hình này không còn được dùng nữa',
|
deprecated: 'Mô hình này không còn được dùng nữa',
|
||||||
},
|
},
|
||||||
|
vision: 'tầm nhìn',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
title: 'Tệp do tác nhân tạo',
|
title: 'Tệp do tác nhân tạo',
|
||||||
|
|
|
||||||
|
|
@ -980,6 +980,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: '此模型已弃用',
|
deprecated: '此模型已弃用',
|
||||||
},
|
},
|
||||||
|
vision: '视觉',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
text: 'agent 生成的内容',
|
text: 'agent 生成的内容',
|
||||||
usage: '模型用量信息',
|
usage: '模型用量信息',
|
||||||
|
|
|
||||||
|
|
@ -906,6 +906,7 @@ const translation = {
|
||||||
modelSelectorTooltips: {
|
modelSelectorTooltips: {
|
||||||
deprecated: '此模型已棄用',
|
deprecated: '此模型已棄用',
|
||||||
},
|
},
|
||||||
|
vision: '視覺',
|
||||||
outputVars: {
|
outputVars: {
|
||||||
files: {
|
files: {
|
||||||
type: '支撐類型。現在僅支援鏡像',
|
type: '支撐類型。現在僅支援鏡像',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue