mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
fix(agent): add agent app duplicate endpoint (#37571)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
872b5a081f
commit
912c0fa8d1
@@ -19,7 +19,7 @@ from models.agent import (
|
||||
)
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
from models.enums import AppStatus
|
||||
from models.model import App, AppMode
|
||||
from models.model import App, AppMode, IconType
|
||||
from models.workflow import Workflow
|
||||
from services.agent.agent_soul_state import agent_soul_has_model
|
||||
from services.agent.composer_validator import ComposerConfigValidator
|
||||
@@ -29,7 +29,10 @@ from services.agent.errors import (
|
||||
AgentNotFoundError,
|
||||
AgentVersionNotFoundError,
|
||||
)
|
||||
from services.app_service import AppService, CreateAppParams
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.entities.agent_entities import RosterAgentCreatePayload, RosterAgentUpdatePayload
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
|
||||
class AgentReferencingWorkflow(TypedDict):
|
||||
@@ -48,6 +51,28 @@ class AgentReferencingWorkflow(TypedDict):
|
||||
|
||||
|
||||
class AgentRosterService:
|
||||
_APP_MODEL_CONFIG_COPY_FIELDS = (
|
||||
"opening_statement",
|
||||
"suggested_questions",
|
||||
"suggested_questions_after_answer",
|
||||
"speech_to_text",
|
||||
"text_to_speech",
|
||||
"more_like_this",
|
||||
"model",
|
||||
"user_input_form",
|
||||
"dataset_query_variable",
|
||||
"pre_prompt",
|
||||
"agent_mode",
|
||||
"sensitive_word_avoidance",
|
||||
"retriever_resource",
|
||||
"prompt_type",
|
||||
"chat_prompt_config",
|
||||
"completion_prompt_config",
|
||||
"dataset_configs",
|
||||
"external_data_tools",
|
||||
"file_upload",
|
||||
)
|
||||
|
||||
def __init__(self, session: Any):
|
||||
self._session = session
|
||||
|
||||
@@ -418,6 +443,142 @@ class AgentRosterService:
|
||||
raise AgentNotFoundError()
|
||||
return app
|
||||
|
||||
def duplicate_agent_app(
|
||||
self,
|
||||
*,
|
||||
tenant_id: str,
|
||||
agent_id: str,
|
||||
account: Any,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
icon_type: Any = None,
|
||||
icon: str | None = None,
|
||||
icon_background: str | None = None,
|
||||
) -> App:
|
||||
source_app = self.get_agent_app_model(tenant_id=tenant_id, agent_id=agent_id)
|
||||
source_agent = self.get_app_backing_agent(tenant_id=tenant_id, app_id=source_app.id)
|
||||
if source_agent is None:
|
||||
raise AgentNotFoundError()
|
||||
|
||||
copied_name = name or self._next_duplicate_agent_name(tenant_id=tenant_id, base_name=source_app.name)
|
||||
copied_description = description if description is not None else source_app.description
|
||||
copied_icon_type = icon_type if icon_type is not None else source_app.icon_type
|
||||
copied_icon = icon if icon is not None else source_app.icon
|
||||
copied_icon_background = icon_background if icon_background is not None else source_app.icon_background
|
||||
|
||||
target_app = AppService().create_app(
|
||||
tenant_id,
|
||||
CreateAppParams(
|
||||
name=copied_name,
|
||||
description=copied_description,
|
||||
mode="agent",
|
||||
agent_role=source_agent.role or "",
|
||||
icon_type=self._normalize_app_icon_type(copied_icon_type),
|
||||
icon=copied_icon,
|
||||
icon_background=copied_icon_background,
|
||||
api_rph=source_app.api_rph or 0,
|
||||
api_rpm=source_app.api_rpm or 0,
|
||||
max_active_requests=source_app.max_active_requests,
|
||||
),
|
||||
account,
|
||||
)
|
||||
|
||||
target_app.enable_site = source_app.enable_site
|
||||
target_app.enable_api = source_app.enable_api
|
||||
target_app.use_icon_as_answer_icon = source_app.use_icon_as_answer_icon
|
||||
target_app.tracing = source_app.tracing
|
||||
|
||||
self._copy_app_model_config(source_app=source_app, target_app=target_app, account_id=account.id)
|
||||
self._copy_agent_active_snapshot(
|
||||
tenant_id=tenant_id,
|
||||
source_agent=source_agent,
|
||||
target_app_id=target_app.id,
|
||||
account_id=account.id,
|
||||
)
|
||||
self._session.commit()
|
||||
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
try:
|
||||
original_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(source_app.id)
|
||||
access_mode = original_settings.access_mode
|
||||
except Exception:
|
||||
access_mode = "public"
|
||||
EnterpriseService.WebAppAuth.update_app_access_mode(target_app.id, access_mode)
|
||||
|
||||
return target_app
|
||||
|
||||
@staticmethod
|
||||
def _normalize_app_icon_type(icon_type: IconType | str | None) -> str | None:
|
||||
if icon_type is None:
|
||||
return None
|
||||
if isinstance(icon_type, IconType):
|
||||
return icon_type.value
|
||||
return icon_type
|
||||
|
||||
def _copy_app_model_config(self, *, source_app: App, target_app: App, account_id: str) -> None:
|
||||
source_config = source_app.app_model_config
|
||||
target_config = target_app.app_model_config
|
||||
if source_config is None or target_config is None:
|
||||
return
|
||||
|
||||
for field_name in self._APP_MODEL_CONFIG_COPY_FIELDS:
|
||||
setattr(target_config, field_name, getattr(source_config, field_name))
|
||||
target_config.updated_by = account_id
|
||||
|
||||
def _copy_agent_active_snapshot(
|
||||
self,
|
||||
*,
|
||||
tenant_id: str,
|
||||
source_agent: Agent,
|
||||
target_app_id: str,
|
||||
account_id: str,
|
||||
) -> None:
|
||||
target_agent = self.get_app_backing_agent(tenant_id=tenant_id, app_id=target_app_id)
|
||||
if target_agent is None:
|
||||
raise AgentNotFoundError()
|
||||
|
||||
source_version = self._get_version(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=source_agent.id,
|
||||
version_id=source_agent.active_config_snapshot_id,
|
||||
)
|
||||
target_version = self._get_version(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=target_agent.id,
|
||||
version_id=target_agent.active_config_snapshot_id,
|
||||
)
|
||||
|
||||
target_version.config_snapshot = AgentSoulConfig.model_validate(source_version.config_snapshot_dict)
|
||||
target_version.summary = source_version.summary
|
||||
target_version.version_note = source_version.version_note
|
||||
target_version.created_by = account_id
|
||||
target_agent.active_config_has_model = agent_soul_has_model(target_version.config_snapshot)
|
||||
target_agent.updated_by = account_id
|
||||
|
||||
def _next_duplicate_agent_name(self, *, tenant_id: str, base_name: str) -> str:
|
||||
suffix = " copy"
|
||||
max_base_len = 255 - len(suffix)
|
||||
first_candidate = f"{base_name[:max_base_len]}{suffix}"
|
||||
candidates = [first_candidate]
|
||||
for index in range(2, 100):
|
||||
numbered_suffix = f" copy {index}"
|
||||
candidates.append(f"{base_name[: 255 - len(numbered_suffix)]}{numbered_suffix}")
|
||||
|
||||
existing_names = set(
|
||||
self._session.scalars(
|
||||
select(Agent.name).where(
|
||||
Agent.tenant_id == tenant_id,
|
||||
Agent.scope == AgentScope.ROSTER,
|
||||
Agent.status == AgentStatus.ACTIVE,
|
||||
Agent.name.in_(candidates),
|
||||
)
|
||||
).all()
|
||||
)
|
||||
for candidate in candidates:
|
||||
if candidate not in existing_names:
|
||||
return candidate
|
||||
return f"{base_name[:245]} copy {int(naive_utc_now().timestamp())}"
|
||||
|
||||
def list_workflows_referencing_app_agent(self, *, tenant_id: str, app_id: str) -> list[AgentReferencingWorkflow]:
|
||||
"""List the workflow apps that reference this Agent App's bound Agent.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user