| @@ -420,10 +420,11 @@ POSITION_PROVIDER_EXCLUDES= | |||
| # Plugin configuration | |||
| PLUGIN_API_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 | |||
| PLUGIN_DAEMON_URL=http://127.0.0.1:5002 | |||
| PLUGIN_API_URL=http://127.0.0.1:5002 | |||
| PLUGIN_REMOTE_INSTALL_PORT=5003 | |||
| PLUGIN_REMOTE_INSTALL_HOST=localhost | |||
| PLUGIN_MAX_PACKAGE_SIZE=15728640 | |||
| INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 | |||
| INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 | |||
| # Marketplace configuration | |||
| @@ -14,12 +14,10 @@ class PluginAgentStrategy(BaseAgentStrategy): | |||
| """ | |||
| tenant_id: str | |||
| plugin_unique_identifier: str | |||
| declaration: AgentStrategyEntity | |||
| def __init__(self, tenant_id: str, plugin_unique_identifier: str, declaration: AgentStrategyEntity): | |||
| def __init__(self, tenant_id: str, declaration: AgentStrategyEntity): | |||
| self.tenant_id = tenant_id | |||
| self.plugin_unique_identifier = plugin_unique_identifier | |||
| self.declaration = declaration | |||
| def get_parameters(self) -> Sequence[AgentStrategyParameter]: | |||
| @@ -255,10 +255,9 @@ class WorkflowCycleManage: | |||
| for workflow_node_execution in running_workflow_node_executions: | |||
| workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value | |||
| workflow_node_execution.error = error | |||
| workflow_node_execution.finished_at = datetime.now(UTC).replace(tzinfo=None) | |||
| workflow_node_execution.elapsed_time = ( | |||
| workflow_node_execution.finished_at - workflow_node_execution.created_at | |||
| ).total_seconds() | |||
| finish_at = datetime.now(UTC).replace(tzinfo=None) | |||
| workflow_node_execution.finished_at = finish_at | |||
| workflow_node_execution.elapsed_time = (finish_at - workflow_node_execution.created_at).total_seconds() | |||
| if trace_manager: | |||
| trace_manager.add_trace_task( | |||
| @@ -136,7 +136,8 @@ def cast_parameter_value(typ: enum.StrEnum, value: Any, /): | |||
| return value | |||
| case _: | |||
| return str(value) | |||
| except ValueError: | |||
| raise | |||
| except Exception: | |||
| raise ValueError(f"The tool parameter value {value} is not in correct type of {as_normal_type(typ)}.") | |||
| @@ -92,7 +92,7 @@ class PluginAgentManager(BasePluginManager): | |||
| response = self._request_with_plugin_daemon_response_stream( | |||
| "POST", | |||
| f"plugin/{tenant_id}/dispatch/agent/invoke", | |||
| f"plugin/{tenant_id}/dispatch/agent_strategy/invoke", | |||
| AgentInvokeMessage, | |||
| data={ | |||
| "user_id": user_id, | |||
| @@ -1,15 +1,23 @@ | |||
| from collections.abc import Generator, Sequence | |||
| from ast import literal_eval | |||
| from collections.abc import Generator, Mapping, Sequence | |||
| from typing import Any, cast | |||
| from core.agent.entities import AgentToolEntity | |||
| from core.agent.plugin_entities import AgentStrategyParameter | |||
| from core.model_manager import ModelManager | |||
| from core.model_runtime.entities.model_entities import ModelType | |||
| from core.plugin.manager.exc import PluginDaemonClientSideError | |||
| from core.tools.entities.tool_entities import ToolProviderType | |||
| from core.tools.tool_manager import ToolManager | |||
| from core.workflow.entities.node_entities import NodeRunResult | |||
| from core.workflow.entities.variable_pool import VariablePool | |||
| from core.workflow.enums import SystemVariableKey | |||
| from core.workflow.nodes.agent.entities import AgentNodeData | |||
| from core.workflow.nodes.base.entities import BaseNodeData | |||
| from core.workflow.nodes.enums import NodeType | |||
| from core.workflow.nodes.event.event import RunCompletedEvent | |||
| from core.workflow.nodes.tool.tool_node import ToolNode | |||
| from core.workflow.utils.variable_template_parser import VariableTemplateParser | |||
| from factories.agent_factory import get_plugin_agent_strategy | |||
| from models.workflow import WorkflowNodeExecutionStatus | |||
| @@ -31,7 +39,6 @@ class AgentNode(ToolNode): | |||
| try: | |||
| strategy = get_plugin_agent_strategy( | |||
| tenant_id=self.tenant_id, | |||
| plugin_unique_identifier=node_data.plugin_unique_identifier, | |||
| agent_strategy_provider_name=node_data.agent_strategy_provider_name, | |||
| agent_strategy_name=node_data.agent_strategy_name, | |||
| ) | |||
| @@ -48,12 +55,12 @@ class AgentNode(ToolNode): | |||
| agent_parameters = strategy.get_parameters() | |||
| # get parameters | |||
| parameters = self._generate_parameters( | |||
| parameters = self._generate_agent_parameters( | |||
| agent_parameters=agent_parameters, | |||
| variable_pool=self.graph_runtime_state.variable_pool, | |||
| node_data=node_data, | |||
| ) | |||
| parameters_for_log = self._generate_parameters( | |||
| parameters_for_log = self._generate_agent_parameters( | |||
| agent_parameters=agent_parameters, | |||
| variable_pool=self.graph_runtime_state.variable_pool, | |||
| node_data=node_data, | |||
| @@ -78,6 +85,7 @@ class AgentNode(ToolNode): | |||
| error=f"Failed to invoke agent: {str(e)}", | |||
| ) | |||
| ) | |||
| return | |||
| try: | |||
| # convert tool messages | |||
| @@ -91,7 +99,7 @@ class AgentNode(ToolNode): | |||
| ) | |||
| ) | |||
| def _generate_parameters( | |||
| def _generate_agent_parameters( | |||
| self, | |||
| *, | |||
| agent_parameters: Sequence[AgentStrategyParameter], | |||
| @@ -130,6 +138,86 @@ class AgentNode(ToolNode): | |||
| parameter_value = segment_group.log if for_log else segment_group.text | |||
| else: | |||
| raise ValueError(f"Unknown agent input type '{agent_input.type}'") | |||
| result[parameter_name] = parameter_value | |||
| value = parameter_value.strip() | |||
| if (parameter_value.startswith("{") and parameter_value.endswith("}")) or ( | |||
| parameter_value.startswith("[") and parameter_value.endswith("]") | |||
| ): | |||
| value = literal_eval(parameter_value) # transform string to python object | |||
| if parameter.type == "array[tools]": | |||
| value = cast(list[dict[str, Any]], value) | |||
| value = [tool for tool in value if tool.get("enabled", False)] | |||
| if not for_log: | |||
| if parameter.type == "array[tools]": | |||
| value = cast(list[dict[str, Any]], value) | |||
| tool_value = [] | |||
| for tool in value: | |||
| entity = AgentToolEntity( | |||
| provider_id=tool.get("provider_name", ""), | |||
| provider_type=ToolProviderType.BUILT_IN, | |||
| tool_name=tool.get("tool_name", ""), | |||
| tool_parameters=tool.get("parameters", {}), | |||
| plugin_unique_identifier=tool.get("plugin_unique_identifier", None), | |||
| ) | |||
| extra = tool.get("extra", {}) | |||
| tool_runtime = ToolManager.get_agent_tool_runtime( | |||
| self.tenant_id, self.app_id, entity, self.invoke_from | |||
| ) | |||
| if tool_runtime.entity.description: | |||
| tool_runtime.entity.description.llm = ( | |||
| extra.get("descrption", "") or tool_runtime.entity.description.llm | |||
| ) | |||
| tool_value.append(tool_runtime.entity.model_dump(mode="json")) | |||
| value = tool_value | |||
| if parameter.type == "model-selector": | |||
| value = cast(dict[str, Any], value) | |||
| model_instance = ModelManager().get_model_instance( | |||
| tenant_id=self.tenant_id, | |||
| provider=value.get("provider", ""), | |||
| model_type=ModelType(value.get("model_type", "")), | |||
| model=value.get("model", ""), | |||
| ) | |||
| models = model_instance.model_type_instance.plugin_model_provider.declaration.models | |||
| finded_model = next((model for model in models if model.model == value.get("model", "")), None) | |||
| value["entity"] = finded_model.model_dump(mode="json") if finded_model else None | |||
| result[parameter_name] = value | |||
| return result | |||
| @classmethod | |||
| def _extract_variable_selector_to_variable_mapping( | |||
| cls, | |||
| *, | |||
| graph_config: Mapping[str, Any], | |||
| node_id: str, | |||
| node_data: BaseNodeData, | |||
| ) -> Mapping[str, Sequence[str]]: | |||
| """ | |||
| Extract variable selector to variable mapping | |||
| :param graph_config: graph config | |||
| :param node_id: node id | |||
| :param node_data: node data | |||
| :return: | |||
| """ | |||
| node_data = cast(AgentNodeData, node_data) | |||
| result = {} | |||
| for parameter_name in node_data.agent_parameters: | |||
| input = node_data.agent_parameters[parameter_name] | |||
| if input.type == "mixed": | |||
| assert isinstance(input.value, str) | |||
| selectors = VariableTemplateParser(input.value).extract_variable_selectors() | |||
| for selector in selectors: | |||
| result[selector.variable] = selector.value_selector | |||
| elif input.type == "variable": | |||
| result[parameter_name] = input.value | |||
| elif input.type == "constant": | |||
| pass | |||
| result = {node_id + "." + key: value for key, value in result.items()} | |||
| return result | |||
| @@ -1,81 +1,18 @@ | |||
| from typing import Any, Literal, Union | |||
| from pydantic import BaseModel, ValidationInfo, field_validator | |||
| from pydantic import BaseModel | |||
| from core.tools.entities.tool_entities import ToolSelector | |||
| from core.workflow.nodes.base.entities import BaseNodeData | |||
| class AgentEntity(BaseModel): | |||
| class AgentNodeData(BaseNodeData): | |||
| agent_strategy_provider_name: str # redundancy | |||
| agent_strategy_name: str | |||
| agent_strategy_label: str # redundancy | |||
| agent_configurations: dict[str, Any] | |||
| plugin_unique_identifier: str | |||
| @field_validator("agent_configurations", mode="before") | |||
| @classmethod | |||
| def validate_agent_configurations(cls, value, values: ValidationInfo): | |||
| if not isinstance(value, dict): | |||
| raise ValueError("agent_configurations must be a dictionary") | |||
| for key in values.data.get("agent_configurations", {}): | |||
| value = values.data.get("agent_configurations", {}).get(key) | |||
| if isinstance(value, dict): | |||
| # convert dict to ToolSelector | |||
| return ToolSelector(**value) | |||
| elif isinstance(value, ToolSelector): | |||
| return value | |||
| elif isinstance(value, list): | |||
| # convert list[ToolSelector] to ToolSelector | |||
| if all(isinstance(val, dict) for val in value): | |||
| return [ToolSelector(**val) for val in value] | |||
| elif all(isinstance(val, ToolSelector) for val in value): | |||
| return value | |||
| else: | |||
| raise ValueError("value must be a list of ToolSelector") | |||
| else: | |||
| raise ValueError("value must be a dictionary or ToolSelector") | |||
| return value | |||
| class AgentNodeData(BaseNodeData, AgentEntity): | |||
| class AgentInput(BaseModel): | |||
| # TODO: check this type | |||
| value: Union[list[str], list[ToolSelector], Any] | |||
| type: Literal["mixed", "variable", "constant"] | |||
| @field_validator("type", mode="before") | |||
| @classmethod | |||
| def check_type(cls, value, validation_info: ValidationInfo): | |||
| typ = value | |||
| value = validation_info.data.get("value") | |||
| if typ == "mixed" and not isinstance(value, str): | |||
| raise ValueError("value must be a string") | |||
| elif typ == "variable": | |||
| if not isinstance(value, list): | |||
| raise ValueError("value must be a list") | |||
| for val in value: | |||
| if not isinstance(val, str): | |||
| raise ValueError("value must be a list of strings") | |||
| elif typ == "constant": | |||
| if isinstance(value, list): | |||
| # convert dict to ToolSelector | |||
| if all(isinstance(val, dict) for val in value) or all( | |||
| isinstance(val, ToolSelector) for val in value | |||
| ): | |||
| return value | |||
| else: | |||
| raise ValueError("value must be a list of ToolSelector") | |||
| elif isinstance(value, dict): | |||
| # convert dict to ToolSelector | |||
| return ToolSelector(**value) | |||
| elif isinstance(value, ToolSelector): | |||
| return value | |||
| else: | |||
| raise ValueError("value must be a list of ToolSelector") | |||
| return typ | |||
| agent_parameters: dict[str, AgentInput] | |||
| @@ -3,13 +3,13 @@ from core.plugin.manager.agent import PluginAgentManager | |||
| def get_plugin_agent_strategy( | |||
| tenant_id: str, plugin_unique_identifier: str, agent_strategy_provider_name: str, agent_strategy_name: str | |||
| tenant_id: str, agent_strategy_provider_name: str, agent_strategy_name: str | |||
| ) -> PluginAgentStrategy: | |||
| # TODO: use contexts to cache the agent provider | |||
| manager = PluginAgentManager() | |||
| agent_provider = manager.fetch_agent_strategy_provider(tenant_id, agent_strategy_provider_name) | |||
| for agent_strategy in agent_provider.declaration.strategies: | |||
| if agent_strategy.identity.name == agent_strategy_name: | |||
| return PluginAgentStrategy(tenant_id, plugin_unique_identifier, agent_strategy) | |||
| return PluginAgentStrategy(tenant_id, agent_strategy) | |||
| raise ValueError(f"Agent strategy {agent_strategy_name} not found") | |||