This commit is contained in:
yujiosaka 2025-12-29 15:26:00 +09:00 committed by GitHub
commit e1b27e2cf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 624 additions and 9 deletions

View File

@ -9,10 +9,11 @@ from sqlalchemy.orm import Session
from core.agent.entities import AgentToolEntity
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.model_manager import ModelInstance, ModelManager
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.utils.encoders import jsonable_encoder
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.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 (
NodeType,
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(
self,
*,
@ -206,11 +223,52 @@ class AgentNode(Node[AgentNodeData]):
except TypeError:
parameter_value = str(agent_input.value)
segment_group = variable_pool.convert_template(parameter_value)
parameter_value = segment_group.log if for_log else segment_group.text
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
# variable_pool.convert_template returns a string,
# so we need to convert it back to a dictionary
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)
except json.JSONDecodeError:
parameter_value = parameter_value

View File

@ -1,18 +1,38 @@
from collections.abc import Sequence
from enum import IntEnum, StrEnum, auto
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.tools.entities.tool_entities import ToolSelector
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):
agent_strategy_provider_name: str # redundancy
agent_strategy_name: str
agent_strategy_label: str # redundancy
memory: MemoryConfig | None = None
vision: VisionConfig = Field(default_factory=VisionConfig)
# The version of the tool parameter.
# If this value is None, it indicates this is a previous version
# and requires using the legacy parameter parsing rules.

View File

@ -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)

View File

@ -6,8 +6,10 @@ import type { StrategyParamItem } from '@/app/components/plugins/types'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { toType } from '@/app/components/tools/utils/to-form-schema'
import { Resolution } from '@/types/app'
import { useStore } from '../../store'
import { AgentStrategy } from '../_base/components/agent-strategy'
import ConfigVision from '../_base/components/config-vision'
import Field from '../_base/components/field'
import MemoryConfig from '../_base/components/memory-config'
import OutputVars, { VarItem } from '../_base/components/output-vars'
@ -40,6 +42,8 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
readOnly,
outputSchema,
handleMemoryChange,
handleVisionEnabledChange,
handleVisionConfigChange,
canChooseMCPTool,
} = useConfig(props.id, props.data)
const { t } = useTranslation()
@ -85,12 +89,11 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
canChooseMCPTool={canChooseMCPTool}
/>
</Field>
<div className="px-4 py-2">
<div className="space-y-4 px-4 py-2">
{isChatMode && currentStrategy?.features?.includes(AgentFeature.HISTORY_MESSAGES) && (
<>
<Split />
<MemoryConfig
className="mt-4"
readonly={readOnly}
config={{ data: inputs.memory }}
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>
<OutputVars>

View File

@ -1,6 +1,11 @@
import type { ToolVarInputs } from '../tool/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 & {
agent_strategy_provider_name?: string
@ -11,6 +16,7 @@ export type AgentNodeType = CommonNodeType & {
output_schema: Record<string, any>
plugin_unique_identifier?: string
memory?: Memory
vision?: AgentVisionConfig
version?: string
tool_node_version?: string
}

View File

@ -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 { AgentNodeType } from './types'
import { produce } from 'immer'
@ -11,6 +11,7 @@ import {
} from '@/app/components/workflow/hooks'
import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins'
import { useStrategyProviderDetail } from '@/service/use-strategy'
import { Resolution } from '@/types/app'
import { isSupportMCP } from '@/utils/plugin-version-feature'
import { VarType as VarKindType } from '../../types'
import useAvailableVarList from '../_base/hooks/use-available-var-list'
@ -204,7 +205,34 @@ const useConfig = (id: string, payload: AgentNodeType) => {
})
setInputs(newInputs)
}, [inputs, setInputs])
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 {
readOnly,
inputs,
@ -221,6 +249,8 @@ const useConfig = (id: string, payload: AgentNodeType) => {
availableNodesWithParent,
outputSchema,
handleMemoryChange,
handleVisionEnabledChange,
handleVisionConfigChange,
isChatMode,
canChooseMCPTool: isSupportMCP(inputs.meta?.version),
}

View File

@ -1024,6 +1024,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'تم إهمال هذا النموذج',
},
vision: 'الرؤية',
outputVars: {
text: 'محتوى تم إنشاؤه بواسطة الوكيل',
usage: 'معلومات استخدام النموذج',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Dieses Modell ist veraltet',
},
vision: 'Vision',
outputVars: {
files: {
type: 'Art der Unterstützung. Jetzt nur noch Image unterstützen',

View File

@ -1024,6 +1024,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'This model is deprecated',
},
vision: 'vision',
outputVars: {
text: 'agent generated content',
usage: 'Model Usage Information',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Este modelo está en desuso',
},
vision: 'visión',
outputVars: {
files: {
url: 'URL de la imagen',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'این مدل منسوخ شده است',
},
vision: 'بینایی',
outputVars: {
files: {
transfer_method: 'روش انتقال. ارزش remote_url یا local_file',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Ce modèle est obsolète',
},
vision: 'vision',
outputVars: {
files: {
title: 'Fichiers générés par lagent',

View File

@ -921,6 +921,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'यह मॉडल अप्रचलित है।',
},
vision: 'दृष्टि',
outputVars: {
files: {
transfer_method: 'स्थानांतरण विधि। मान या तो remote_url है या local_file।',

View File

@ -940,6 +940,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Model ini tidak digunakan lagi',
},
vision: 'penglihatan',
outputVars: {
files: {
transfer_method: 'Metode transfer. Nilai adalah remote_url atau local_file',

View File

@ -927,6 +927,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Questo modello è deprecato',
},
vision: 'vision',
outputVars: {
files: {
type: 'Tipo di supporto. Ora supporta solo l\'immagine',

View File

@ -960,6 +960,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'このモデルは廃止されました',
},
vision: 'ビジョン',
outputVars: {
files: {
url: '画像の URL',

View File

@ -943,6 +943,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: '이 모델은 더 이상 사용되지 않습니다.',
},
vision: '비전',
outputVars: {
files: {
url: '이미지 URL',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Ten model jest przestarzały',
},
vision: 'wizja',
outputVars: {
files: {
title: 'Pliki generowane przez agenta',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Este modelo está obsoleto',
},
vision: 'visão',
outputVars: {
files: {
type: 'Tipo de suporte. Agora suporta apenas imagem',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Acest model este învechit',
},
vision: 'viziune',
outputVars: {
files: {
upload_file_id: 'Încărcați ID-ul fișierului',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Эта модель устарела',
},
vision: 'зрение',
outputVars: {
files: {
transfer_method: 'Способ переноса. Ценность составляет remote_url или local_file',

View File

@ -940,6 +940,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Ta model je zastarelo',
},
vision: 'vizija',
outputVars: {
files: {
type: 'Vrsta podpore. Zdaj podpiramo samo slike.',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'โมเดลนี้เลิกใช้แล้ว',
},
vision: 'การมองเห็น',
outputVars: {
files: {
transfer_method: 'วิธีการโอน ค่าเป็น remote_url หรือ local_file',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Bu model kullanım dışıdır',
},
vision: 'görsel',
outputVars: {
files: {
upload_file_id: 'Dosya kimliğini karşıya yükle',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Ця модель вважається застарілою',
},
vision: 'бачення',
outputVars: {
files: {
upload_file_id: 'Завантажити ідентифікатор файлу',

View File

@ -901,6 +901,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: 'Mô hình này không còn được dùng nữa',
},
vision: 'tầm nhìn',
outputVars: {
files: {
title: 'Tệp do tác nhân tạo',

View File

@ -980,6 +980,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: '此模型已弃用',
},
vision: '视觉',
outputVars: {
text: 'agent 生成的内容',
usage: '模型用量信息',

View File

@ -906,6 +906,7 @@ const translation = {
modelSelectorTooltips: {
deprecated: '此模型已棄用',
},
vision: '視覺',
outputVars: {
files: {
type: '支撐類型。現在僅支援鏡像',