mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-19 01:15:53 +08:00
feat: add fc agent mode support
This commit is contained in:
parent
fb7b2c8ff3
commit
b56d2b739b
@ -420,10 +420,11 @@ POSITION_PROVIDER_EXCLUDES=
|
|||||||
|
|
||||||
# Plugin configuration
|
# Plugin configuration
|
||||||
PLUGIN_API_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1
|
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_PORT=5003
|
||||||
PLUGIN_REMOTE_INSTALL_HOST=localhost
|
PLUGIN_REMOTE_INSTALL_HOST=localhost
|
||||||
PLUGIN_MAX_PACKAGE_SIZE=15728640
|
PLUGIN_MAX_PACKAGE_SIZE=15728640
|
||||||
|
INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1
|
||||||
INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1
|
INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1
|
||||||
|
|
||||||
# Marketplace configuration
|
# Marketplace configuration
|
||||||
|
@ -14,12 +14,10 @@ class PluginAgentStrategy(BaseAgentStrategy):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
tenant_id: str
|
tenant_id: str
|
||||||
plugin_unique_identifier: str
|
|
||||||
declaration: AgentStrategyEntity
|
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.tenant_id = tenant_id
|
||||||
self.plugin_unique_identifier = plugin_unique_identifier
|
|
||||||
self.declaration = declaration
|
self.declaration = declaration
|
||||||
|
|
||||||
def get_parameters(self) -> Sequence[AgentStrategyParameter]:
|
def get_parameters(self) -> Sequence[AgentStrategyParameter]:
|
||||||
|
@ -255,10 +255,9 @@ class WorkflowCycleManage:
|
|||||||
for workflow_node_execution in running_workflow_node_executions:
|
for workflow_node_execution in running_workflow_node_executions:
|
||||||
workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
||||||
workflow_node_execution.error = error
|
workflow_node_execution.error = error
|
||||||
workflow_node_execution.finished_at = datetime.now(UTC).replace(tzinfo=None)
|
finish_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
workflow_node_execution.elapsed_time = (
|
workflow_node_execution.finished_at = finish_at
|
||||||
workflow_node_execution.finished_at - workflow_node_execution.created_at
|
workflow_node_execution.elapsed_time = (finish_at - workflow_node_execution.created_at).total_seconds()
|
||||||
).total_seconds()
|
|
||||||
|
|
||||||
if trace_manager:
|
if trace_manager:
|
||||||
trace_manager.add_trace_task(
|
trace_manager.add_trace_task(
|
||||||
|
@ -136,7 +136,8 @@ def cast_parameter_value(typ: enum.StrEnum, value: Any, /):
|
|||||||
return value
|
return value
|
||||||
case _:
|
case _:
|
||||||
return str(value)
|
return str(value)
|
||||||
|
except ValueError:
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError(f"The tool parameter value {value} is not in correct type of {as_normal_type(typ)}.")
|
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(
|
response = self._request_with_plugin_daemon_response_stream(
|
||||||
"POST",
|
"POST",
|
||||||
f"plugin/{tenant_id}/dispatch/agent/invoke",
|
f"plugin/{tenant_id}/dispatch/agent_strategy/invoke",
|
||||||
AgentInvokeMessage,
|
AgentInvokeMessage,
|
||||||
data={
|
data={
|
||||||
"user_id": user_id,
|
"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 typing import Any, cast
|
||||||
|
|
||||||
|
from core.agent.entities import AgentToolEntity
|
||||||
from core.agent.plugin_entities import AgentStrategyParameter
|
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.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.node_entities import NodeRunResult
|
||||||
from core.workflow.entities.variable_pool import VariablePool
|
from core.workflow.entities.variable_pool import VariablePool
|
||||||
from core.workflow.enums import SystemVariableKey
|
from core.workflow.enums import SystemVariableKey
|
||||||
from core.workflow.nodes.agent.entities import AgentNodeData
|
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.enums import NodeType
|
||||||
from core.workflow.nodes.event.event import RunCompletedEvent
|
from core.workflow.nodes.event.event import RunCompletedEvent
|
||||||
from core.workflow.nodes.tool.tool_node import ToolNode
|
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 factories.agent_factory import get_plugin_agent_strategy
|
||||||
from models.workflow import WorkflowNodeExecutionStatus
|
from models.workflow import WorkflowNodeExecutionStatus
|
||||||
|
|
||||||
@ -31,7 +39,6 @@ class AgentNode(ToolNode):
|
|||||||
try:
|
try:
|
||||||
strategy = get_plugin_agent_strategy(
|
strategy = get_plugin_agent_strategy(
|
||||||
tenant_id=self.tenant_id,
|
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_provider_name=node_data.agent_strategy_provider_name,
|
||||||
agent_strategy_name=node_data.agent_strategy_name,
|
agent_strategy_name=node_data.agent_strategy_name,
|
||||||
)
|
)
|
||||||
@ -48,12 +55,12 @@ class AgentNode(ToolNode):
|
|||||||
agent_parameters = strategy.get_parameters()
|
agent_parameters = strategy.get_parameters()
|
||||||
|
|
||||||
# get parameters
|
# get parameters
|
||||||
parameters = self._generate_parameters(
|
parameters = self._generate_agent_parameters(
|
||||||
agent_parameters=agent_parameters,
|
agent_parameters=agent_parameters,
|
||||||
variable_pool=self.graph_runtime_state.variable_pool,
|
variable_pool=self.graph_runtime_state.variable_pool,
|
||||||
node_data=node_data,
|
node_data=node_data,
|
||||||
)
|
)
|
||||||
parameters_for_log = self._generate_parameters(
|
parameters_for_log = self._generate_agent_parameters(
|
||||||
agent_parameters=agent_parameters,
|
agent_parameters=agent_parameters,
|
||||||
variable_pool=self.graph_runtime_state.variable_pool,
|
variable_pool=self.graph_runtime_state.variable_pool,
|
||||||
node_data=node_data,
|
node_data=node_data,
|
||||||
@ -78,6 +85,7 @@ class AgentNode(ToolNode):
|
|||||||
error=f"Failed to invoke agent: {str(e)}",
|
error=f"Failed to invoke agent: {str(e)}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# convert tool messages
|
# convert tool messages
|
||||||
@ -91,7 +99,7 @@ class AgentNode(ToolNode):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_parameters(
|
def _generate_agent_parameters(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
agent_parameters: Sequence[AgentStrategyParameter],
|
agent_parameters: Sequence[AgentStrategyParameter],
|
||||||
@ -130,6 +138,86 @@ class AgentNode(ToolNode):
|
|||||||
parameter_value = segment_group.log if for_log else segment_group.text
|
parameter_value = segment_group.log if for_log else segment_group.text
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown agent input type '{agent_input.type}'")
|
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
|
return result
|
||||||
|
@ -1,81 +1,18 @@
|
|||||||
from typing import Any, Literal, Union
|
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.tools.entities.tool_entities import ToolSelector
|
||||||
from core.workflow.nodes.base.entities import BaseNodeData
|
from core.workflow.nodes.base.entities import BaseNodeData
|
||||||
|
|
||||||
|
|
||||||
class AgentEntity(BaseModel):
|
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
|
||||||
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):
|
class AgentInput(BaseModel):
|
||||||
# TODO: check this type
|
|
||||||
value: Union[list[str], list[ToolSelector], Any]
|
value: Union[list[str], list[ToolSelector], Any]
|
||||||
type: Literal["mixed", "variable", "constant"]
|
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]
|
agent_parameters: dict[str, AgentInput]
|
||||||
|
@ -3,13 +3,13 @@ from core.plugin.manager.agent import PluginAgentManager
|
|||||||
|
|
||||||
|
|
||||||
def get_plugin_agent_strategy(
|
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:
|
) -> PluginAgentStrategy:
|
||||||
# TODO: use contexts to cache the agent provider
|
# TODO: use contexts to cache the agent provider
|
||||||
manager = PluginAgentManager()
|
manager = PluginAgentManager()
|
||||||
agent_provider = manager.fetch_agent_strategy_provider(tenant_id, agent_strategy_provider_name)
|
agent_provider = manager.fetch_agent_strategy_provider(tenant_id, agent_strategy_provider_name)
|
||||||
for agent_strategy in agent_provider.declaration.strategies:
|
for agent_strategy in agent_provider.declaration.strategies:
|
||||||
if agent_strategy.identity.name == agent_strategy_name:
|
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")
|
raise ValueError(f"Agent strategy {agent_strategy_name} not found")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user