feat: add fc agent mode support

This commit is contained in:
Novice Lee 2025-01-08 07:41:17 +08:00
parent fb7b2c8ff3
commit b56d2b739b
8 changed files with 107 additions and 83 deletions

View File

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

View File

@ -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]:

View File

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

View File

@ -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)}.")

View File

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

View File

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

View File

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

View File

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