mirror of
https://github.com/langgenius/dify.git
synced 2026-09-01 21:55:46 +08:00
241 lines
9.6 KiB
Python
241 lines
9.6 KiB
Python
"""Database repository for externally visible app definitions."""
|
|
|
|
from typing import Any, cast, override
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from core.agent.publish_visibility import agent_has_workflow_callable_active_snapshot
|
|
from core.app.apps.agent_app.app_feature_projection import merge_agent_app_features
|
|
from core.app.apps.agent_app.app_variable_projection import agent_app_variables_to_user_input_form
|
|
from core.app.apps.agent_app.errors import AgentAppGeneratorError, AgentAppNotPublishedError
|
|
from models.account import Tenant, TenantStatus
|
|
from models.agent import AgentConfigSnapshot
|
|
from models.agent_config_entities import AgentSoulConfig
|
|
from models.model import App, AppMode, AppModelConfig, Site, load_annotation_reply_config
|
|
from models.tools import ApiToolProvider
|
|
from models.workflow import Workflow
|
|
from services.app_definition_query_service import (
|
|
AppDefinitionQuery,
|
|
AppDefinitionSummary,
|
|
AppParameterConfig,
|
|
AppSiteConfiguration,
|
|
AppToolIconSource,
|
|
)
|
|
from services.web_app_runtime_query_service import WebAppRuntimeRecord
|
|
|
|
|
|
def _map_site_configuration(site: Site) -> AppSiteConfiguration:
|
|
return AppSiteConfiguration(
|
|
title=site.title,
|
|
chat_color_theme=site.chat_color_theme,
|
|
chat_color_theme_inverted=site.chat_color_theme_inverted,
|
|
icon_type=site.icon_type.value if site.icon_type is not None else None,
|
|
icon=site.icon,
|
|
icon_background=site.icon_background,
|
|
description=site.description,
|
|
copyright=site.copyright,
|
|
privacy_policy=site.privacy_policy,
|
|
input_placeholder=site.input_placeholder,
|
|
custom_disclaimer=site.custom_disclaimer,
|
|
default_language=site.default_language,
|
|
prompt_public=site.prompt_public,
|
|
show_workflow_steps=site.show_workflow_steps,
|
|
use_icon_as_answer_icon=site.use_icon_as_answer_icon,
|
|
)
|
|
|
|
|
|
def _get_public_agent_parameter_config(app: App, *, session: Session) -> AppParameterConfig:
|
|
app_model_config = app.app_model_config_with_session(session=session)
|
|
agent = app.agent_app_binding_with_session(session=session)
|
|
if agent is None:
|
|
raise AgentAppGeneratorError("Agent App has no bound Agent")
|
|
if not agent_has_workflow_callable_active_snapshot(session=session, agent=agent):
|
|
raise AgentAppNotPublishedError("Agent has not been published")
|
|
|
|
snapshot = session.scalar(
|
|
select(AgentConfigSnapshot)
|
|
.where(
|
|
AgentConfigSnapshot.tenant_id == app.tenant_id,
|
|
AgentConfigSnapshot.agent_id == agent.id,
|
|
AgentConfigSnapshot.id == agent.active_config_snapshot_id,
|
|
)
|
|
.limit(1)
|
|
)
|
|
if snapshot is None:
|
|
raise AgentAppGeneratorError("Agent published version not found")
|
|
|
|
agent_soul = AgentSoulConfig.model_validate(snapshot.config_snapshot_dict)
|
|
annotation_reply = load_annotation_reply_config(session, app.id) if app_model_config else None
|
|
return AppParameterConfig(
|
|
features_dict=merge_agent_app_features(
|
|
agent_soul=agent_soul,
|
|
app_model_config=app_model_config,
|
|
annotation_reply=annotation_reply,
|
|
),
|
|
user_input_form=agent_app_variables_to_user_input_form(agent_soul.app_variables),
|
|
)
|
|
|
|
|
|
class AppDefinitionQueryRepository(AppDefinitionQuery):
|
|
def __init__(self, *, session_factory: sessionmaker[Session]) -> None:
|
|
self._session_factory = session_factory
|
|
|
|
@override
|
|
def get_published_parameter_config(
|
|
self,
|
|
app_id: str,
|
|
*,
|
|
public_runtime: bool = False,
|
|
) -> AppParameterConfig | None:
|
|
with self._session_factory() as session:
|
|
app = session.get(App, app_id)
|
|
if app is None:
|
|
return None
|
|
|
|
if public_runtime and app.mode == AppMode.AGENT:
|
|
return _get_public_agent_parameter_config(app, session=session)
|
|
|
|
if app.mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:
|
|
workflow = app.workflow_with_session(session=session)
|
|
if workflow is None:
|
|
return None
|
|
|
|
return AppParameterConfig(
|
|
features_dict=workflow.features_dict,
|
|
user_input_form=cast(list[dict[str, Any]], workflow.user_input_form(to_old_structure=True)),
|
|
)
|
|
|
|
app_model_config = app.app_model_config_with_session(session=session)
|
|
if app_model_config is None:
|
|
return None
|
|
|
|
features_dict = app_model_config.to_dict(
|
|
annotation_reply=load_annotation_reply_config(session, app.id),
|
|
)
|
|
return AppParameterConfig(
|
|
features_dict=features_dict,
|
|
user_input_form=cast(list[dict[str, Any]], features_dict.get("user_input_form", [])),
|
|
)
|
|
|
|
@override
|
|
def get_tool_icon_sources(self, app_id: str) -> tuple[AppToolIconSource, ...] | None:
|
|
with self._session_factory() as session:
|
|
app = session.get(App, app_id)
|
|
if app is None:
|
|
return None
|
|
|
|
records: list[AppToolIconSource] = []
|
|
for tool in self._get_tools(session, app):
|
|
if len(tool) < 4:
|
|
continue
|
|
|
|
provider_type = str(tool.get("provider_type", ""))
|
|
provider_id = str(tool.get("provider_id", ""))
|
|
tool_name = str(tool.get("tool_name", ""))
|
|
provider_icon: str | None = None
|
|
if provider_type == "api":
|
|
try:
|
|
provider = session.get(ApiToolProvider, provider_id)
|
|
provider_icon = provider.icon if provider is not None else None
|
|
except Exception:
|
|
# Preserve the legacy response fallback when a provider cannot be loaded.
|
|
provider_icon = None
|
|
|
|
records.append(
|
|
AppToolIconSource(
|
|
provider_type=provider_type,
|
|
provider_id=provider_id,
|
|
tool_name=tool_name,
|
|
provider_icon=provider_icon,
|
|
)
|
|
)
|
|
|
|
return tuple(records)
|
|
|
|
@override
|
|
def get_summary(self, app_id: str) -> AppDefinitionSummary | None:
|
|
with self._session_factory() as session:
|
|
app = session.get(App, app_id)
|
|
if app is None:
|
|
return None
|
|
|
|
return AppDefinitionSummary(
|
|
name=app.name,
|
|
description=app.description,
|
|
tags=tuple(tag.name for tag in app.tags_with_session(session=session)),
|
|
mode=app.mode.value,
|
|
author_name=app.author_name_with_session(session=session),
|
|
)
|
|
|
|
@override
|
|
def get_site_configuration(self, app_id: str) -> AppSiteConfiguration | None:
|
|
with self._session_factory() as session:
|
|
site = session.scalar(select(Site).where(Site.app_id == app_id).limit(1))
|
|
if site is None:
|
|
return None
|
|
|
|
return _map_site_configuration(site)
|
|
|
|
def get_runtime_record(self, app_id: str) -> WebAppRuntimeRecord | None:
|
|
with self._session_factory() as session:
|
|
app = session.get(App, app_id)
|
|
if app is None:
|
|
return None
|
|
|
|
site = session.scalar(select(Site).where(Site.app_id == app_id).limit(1))
|
|
if site is None:
|
|
return None
|
|
|
|
tenant = session.get(Tenant, app.tenant_id)
|
|
if tenant is None:
|
|
return None
|
|
|
|
app_id = app.id
|
|
tenant_id = app.tenant_id
|
|
enable_site = app.enable_site
|
|
site_configuration = _map_site_configuration(site)
|
|
plan = tenant.plan
|
|
tenant_status = tenant.status.value
|
|
tenant_custom_config_json = tenant.custom_config
|
|
mode = AppMode.value_of(app.mode).value
|
|
if tenant.status != TenantStatus.ARCHIVE:
|
|
mode = AppMode.value_of(app.mode_compatible_with_agent_with_session(session=session)).value
|
|
return WebAppRuntimeRecord(
|
|
app_id=app_id,
|
|
tenant_id=tenant_id,
|
|
mode=mode,
|
|
enable_site=enable_site,
|
|
site=site_configuration,
|
|
plan=plan,
|
|
tenant_status=tenant_status,
|
|
tenant_custom_config_json=tenant_custom_config_json,
|
|
)
|
|
|
|
@staticmethod
|
|
def _get_tools(session: Session, app: App) -> list[dict[str, Any]]:
|
|
if app.mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:
|
|
workflow = session.get(Workflow, app.workflow_id) if app.workflow_id else None
|
|
if workflow is None:
|
|
return []
|
|
|
|
tools: list[dict[str, Any]] = []
|
|
nodes = cast(list[dict[str, Any]], workflow.graph_dict.get("nodes", []))
|
|
for node in nodes:
|
|
node_data = node.get("data", {})
|
|
if node_data.get("type") == "tool":
|
|
tools.append(
|
|
{
|
|
"provider_type": node_data.get("provider_type"),
|
|
"provider_id": node_data.get("provider_id"),
|
|
"tool_name": node_data.get("tool_name"),
|
|
"tool_parameters": {},
|
|
}
|
|
)
|
|
return tools
|
|
|
|
app_model_config = session.get(AppModelConfig, app.app_model_config_id) if app.app_model_config_id else None
|
|
if app_model_config is None:
|
|
return []
|
|
return cast(list[dict[str, Any]], app_model_config.agent_mode_dict.get("tools", []))
|