mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
Merge branch 'main' into feat/plugin
This commit is contained in:
@ -47,7 +47,7 @@ class AccountService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_user(user_id: str) -> Account:
|
||||
def load_user(user_id: str) -> None | Account:
|
||||
account = Account.query.filter_by(id=user_id).first()
|
||||
if not account:
|
||||
return None
|
||||
@ -55,7 +55,7 @@ class AccountService:
|
||||
if account.status in [AccountStatus.BANNED.value, AccountStatus.CLOSED.value]:
|
||||
raise Unauthorized("Account is banned or closed.")
|
||||
|
||||
current_tenant = TenantAccountJoin.query.filter_by(account_id=account.id, current=True).first()
|
||||
current_tenant: TenantAccountJoin = TenantAccountJoin.query.filter_by(account_id=account.id, current=True).first()
|
||||
if current_tenant:
|
||||
account.current_tenant_id = current_tenant.tenant_id
|
||||
else:
|
||||
|
||||
411
api/services/app_dsl_service.py
Normal file
411
api/services/app_dsl_service.py
Normal file
@ -0,0 +1,411 @@
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
import yaml # type: ignore
|
||||
|
||||
from core.app.segments import factory
|
||||
from events.app_event import app_model_config_was_updated, app_was_created
|
||||
from extensions.ext_database import db
|
||||
from models.account import Account
|
||||
from models.model import App, AppMode, AppModelConfig
|
||||
from models.workflow import Workflow
|
||||
from services.workflow_service import WorkflowService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
current_dsl_version = "0.1.0"
|
||||
dsl_to_dify_version_mapping: dict[str, str] = {
|
||||
"0.1.0": "0.6.0", # dsl version -> from dify version
|
||||
}
|
||||
|
||||
|
||||
class AppDslService:
|
||||
@classmethod
|
||||
def import_and_create_new_app_from_url(cls, tenant_id: str, url: str, args: dict, account: Account) -> App:
|
||||
"""
|
||||
Import app dsl from url and create new app
|
||||
:param tenant_id: tenant id
|
||||
:param url: import url
|
||||
:param args: request args
|
||||
:param account: Account instance
|
||||
"""
|
||||
try:
|
||||
max_size = 10 * 1024 * 1024 # 10MB
|
||||
timeout = httpx.Timeout(10.0)
|
||||
with httpx.stream("GET", url.strip(), follow_redirects=True, timeout=timeout) as response:
|
||||
response.raise_for_status()
|
||||
total_size = 0
|
||||
content = b""
|
||||
for chunk in response.iter_bytes():
|
||||
total_size += len(chunk)
|
||||
if total_size > max_size:
|
||||
raise ValueError("File size exceeds the limit of 10MB")
|
||||
content += chunk
|
||||
except httpx.HTTPStatusError as http_err:
|
||||
raise ValueError(f"HTTP error occurred: {http_err}")
|
||||
except httpx.RequestError as req_err:
|
||||
raise ValueError(f"Request error occurred: {req_err}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to fetch DSL from URL: {e}")
|
||||
|
||||
if not content:
|
||||
raise ValueError("Empty content from url")
|
||||
|
||||
try:
|
||||
data = content.decode("utf-8")
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(f"Error decoding content: {e}")
|
||||
|
||||
return cls.import_and_create_new_app(tenant_id, data, args, account)
|
||||
|
||||
@classmethod
|
||||
def import_and_create_new_app(cls, tenant_id: str, data: str, args: dict, account: Account) -> App:
|
||||
"""
|
||||
Import app dsl and create new app
|
||||
:param tenant_id: tenant id
|
||||
:param data: import data
|
||||
:param args: request args
|
||||
:param account: Account instance
|
||||
"""
|
||||
try:
|
||||
import_data = yaml.safe_load(data)
|
||||
except yaml.YAMLError:
|
||||
raise ValueError("Invalid YAML format in data argument.")
|
||||
|
||||
# check or repair dsl version
|
||||
import_data = cls._check_or_fix_dsl(import_data)
|
||||
|
||||
app_data = import_data.get('app')
|
||||
if not app_data:
|
||||
raise ValueError("Missing app in data argument")
|
||||
|
||||
# get app basic info
|
||||
name = args.get("name") if args.get("name") else app_data.get('name')
|
||||
description = args.get("description") if args.get("description") else app_data.get('description', '')
|
||||
icon = args.get("icon") if args.get("icon") else app_data.get('icon')
|
||||
icon_background = args.get("icon_background") if args.get("icon_background") \
|
||||
else app_data.get('icon_background')
|
||||
|
||||
# import dsl and create app
|
||||
app_mode = AppMode.value_of(app_data.get('mode'))
|
||||
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
app = cls._import_and_create_new_workflow_based_app(
|
||||
tenant_id=tenant_id,
|
||||
app_mode=app_mode,
|
||||
workflow_data=import_data.get('workflow'),
|
||||
account=account,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
icon_background=icon_background
|
||||
)
|
||||
elif app_mode in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.COMPLETION]:
|
||||
app = cls._import_and_create_new_model_config_based_app(
|
||||
tenant_id=tenant_id,
|
||||
app_mode=app_mode,
|
||||
model_config_data=import_data.get('model_config'),
|
||||
account=account,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
icon_background=icon_background
|
||||
)
|
||||
else:
|
||||
raise ValueError("Invalid app mode")
|
||||
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def import_and_overwrite_workflow(cls, app_model: App, data: str, account: Account) -> Workflow:
|
||||
"""
|
||||
Import app dsl and overwrite workflow
|
||||
:param app_model: App instance
|
||||
:param data: import data
|
||||
:param account: Account instance
|
||||
"""
|
||||
try:
|
||||
import_data = yaml.safe_load(data)
|
||||
except yaml.YAMLError:
|
||||
raise ValueError("Invalid YAML format in data argument.")
|
||||
|
||||
# check or repair dsl version
|
||||
import_data = cls._check_or_fix_dsl(import_data)
|
||||
|
||||
app_data = import_data.get('app')
|
||||
if not app_data:
|
||||
raise ValueError("Missing app in data argument")
|
||||
|
||||
# import dsl and overwrite app
|
||||
app_mode = AppMode.value_of(app_data.get('mode'))
|
||||
if app_mode not in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
raise ValueError("Only support import workflow in advanced-chat or workflow app.")
|
||||
|
||||
if app_data.get('mode') != app_model.mode:
|
||||
raise ValueError(
|
||||
f"App mode {app_data.get('mode')} is not matched with current app mode {app_mode.value}")
|
||||
|
||||
return cls._import_and_overwrite_workflow_based_app(
|
||||
app_model=app_model,
|
||||
workflow_data=import_data.get('workflow'),
|
||||
account=account,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def export_dsl(cls, app_model: App, include_secret:bool = False) -> str:
|
||||
"""
|
||||
Export app
|
||||
:param app_model: App instance
|
||||
:return:
|
||||
"""
|
||||
app_mode = AppMode.value_of(app_model.mode)
|
||||
|
||||
export_data = {
|
||||
"version": current_dsl_version,
|
||||
"kind": "app",
|
||||
"app": {
|
||||
"name": app_model.name,
|
||||
"mode": app_model.mode,
|
||||
"icon": app_model.icon,
|
||||
"icon_background": app_model.icon_background,
|
||||
"description": app_model.description
|
||||
}
|
||||
}
|
||||
|
||||
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
cls._append_workflow_export_data(export_data=export_data, app_model=app_model, include_secret=include_secret)
|
||||
else:
|
||||
cls._append_model_config_export_data(export_data, app_model)
|
||||
|
||||
return yaml.dump(export_data)
|
||||
|
||||
@classmethod
|
||||
def _check_or_fix_dsl(cls, import_data: dict) -> dict:
|
||||
"""
|
||||
Check or fix dsl
|
||||
|
||||
:param import_data: import data
|
||||
"""
|
||||
if not import_data.get('version'):
|
||||
import_data['version'] = "0.1.0"
|
||||
|
||||
if not import_data.get('kind') or import_data.get('kind') != "app":
|
||||
import_data['kind'] = "app"
|
||||
|
||||
if import_data.get('version') != current_dsl_version:
|
||||
# Currently only one DSL version, so no difference checks or compatibility fixes will be performed.
|
||||
logger.warning(f"DSL version {import_data.get('version')} is not compatible "
|
||||
f"with current version {current_dsl_version}, related to "
|
||||
f"Dify version {dsl_to_dify_version_mapping.get(current_dsl_version)}.")
|
||||
|
||||
return import_data
|
||||
|
||||
@classmethod
|
||||
def _import_and_create_new_workflow_based_app(cls,
|
||||
tenant_id: str,
|
||||
app_mode: AppMode,
|
||||
workflow_data: dict,
|
||||
account: Account,
|
||||
name: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
icon_background: str) -> App:
|
||||
"""
|
||||
Import app dsl and create new workflow based app
|
||||
|
||||
:param tenant_id: tenant id
|
||||
:param app_mode: app mode
|
||||
:param workflow_data: workflow data
|
||||
:param account: Account instance
|
||||
:param name: app name
|
||||
:param description: app description
|
||||
:param icon: app icon
|
||||
:param icon_background: app icon background
|
||||
"""
|
||||
if not workflow_data:
|
||||
raise ValueError("Missing workflow in data argument "
|
||||
"when app mode is advanced-chat or workflow")
|
||||
|
||||
app = cls._create_app(
|
||||
tenant_id=tenant_id,
|
||||
app_mode=app_mode,
|
||||
account=account,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
icon_background=icon_background
|
||||
)
|
||||
|
||||
# init draft workflow
|
||||
environment_variables_list = workflow_data.get('environment_variables') or []
|
||||
environment_variables = [factory.build_variable_from_mapping(obj) for obj in environment_variables_list]
|
||||
workflow_service = WorkflowService()
|
||||
draft_workflow = workflow_service.sync_draft_workflow(
|
||||
app_model=app,
|
||||
graph=workflow_data.get('graph', {}),
|
||||
features=workflow_data.get('../core/app/features', {}),
|
||||
unique_hash=None,
|
||||
account=account,
|
||||
environment_variables=environment_variables,
|
||||
)
|
||||
workflow_service.publish_workflow(
|
||||
app_model=app,
|
||||
account=account,
|
||||
draft_workflow=draft_workflow
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def _import_and_overwrite_workflow_based_app(cls,
|
||||
app_model: App,
|
||||
workflow_data: dict,
|
||||
account: Account) -> Workflow:
|
||||
"""
|
||||
Import app dsl and overwrite workflow based app
|
||||
|
||||
:param app_model: App instance
|
||||
:param workflow_data: workflow data
|
||||
:param account: Account instance
|
||||
"""
|
||||
if not workflow_data:
|
||||
raise ValueError("Missing workflow in data argument "
|
||||
"when app mode is advanced-chat or workflow")
|
||||
|
||||
# fetch draft workflow by app_model
|
||||
workflow_service = WorkflowService()
|
||||
current_draft_workflow = workflow_service.get_draft_workflow(app_model=app_model)
|
||||
if current_draft_workflow:
|
||||
unique_hash = current_draft_workflow.unique_hash
|
||||
else:
|
||||
unique_hash = None
|
||||
|
||||
# sync draft workflow
|
||||
environment_variables_list = workflow_data.get('environment_variables') or []
|
||||
environment_variables = [factory.build_variable_from_mapping(obj) for obj in environment_variables_list]
|
||||
draft_workflow = workflow_service.sync_draft_workflow(
|
||||
app_model=app_model,
|
||||
graph=workflow_data.get('graph', {}),
|
||||
features=workflow_data.get('features', {}),
|
||||
unique_hash=unique_hash,
|
||||
account=account,
|
||||
environment_variables=environment_variables,
|
||||
)
|
||||
|
||||
return draft_workflow
|
||||
|
||||
@classmethod
|
||||
def _import_and_create_new_model_config_based_app(cls,
|
||||
tenant_id: str,
|
||||
app_mode: AppMode,
|
||||
model_config_data: dict,
|
||||
account: Account,
|
||||
name: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
icon_background: str) -> App:
|
||||
"""
|
||||
Import app dsl and create new model config based app
|
||||
|
||||
:param tenant_id: tenant id
|
||||
:param app_mode: app mode
|
||||
:param model_config_data: model config data
|
||||
:param account: Account instance
|
||||
:param name: app name
|
||||
:param description: app description
|
||||
:param icon: app icon
|
||||
:param icon_background: app icon background
|
||||
"""
|
||||
if not model_config_data:
|
||||
raise ValueError("Missing model_config in data argument "
|
||||
"when app mode is chat, agent-chat or completion")
|
||||
|
||||
app = cls._create_app(
|
||||
tenant_id=tenant_id,
|
||||
app_mode=app_mode,
|
||||
account=account,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
icon_background=icon_background
|
||||
)
|
||||
|
||||
app_model_config = AppModelConfig()
|
||||
app_model_config = app_model_config.from_model_config_dict(model_config_data)
|
||||
app_model_config.app_id = app.id
|
||||
|
||||
db.session.add(app_model_config)
|
||||
db.session.commit()
|
||||
|
||||
app.app_model_config_id = app_model_config.id
|
||||
|
||||
app_model_config_was_updated.send(
|
||||
app,
|
||||
app_model_config=app_model_config
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def _create_app(cls,
|
||||
tenant_id: str,
|
||||
app_mode: AppMode,
|
||||
account: Account,
|
||||
name: str,
|
||||
description: str,
|
||||
icon: str,
|
||||
icon_background: str) -> App:
|
||||
"""
|
||||
Create new app
|
||||
|
||||
:param tenant_id: tenant id
|
||||
:param app_mode: app mode
|
||||
:param account: Account instance
|
||||
:param name: app name
|
||||
:param description: app description
|
||||
:param icon: app icon
|
||||
:param icon_background: app icon background
|
||||
"""
|
||||
app = App(
|
||||
tenant_id=tenant_id,
|
||||
mode=app_mode.value,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=icon,
|
||||
icon_background=icon_background,
|
||||
enable_site=True,
|
||||
enable_api=True
|
||||
)
|
||||
|
||||
db.session.add(app)
|
||||
db.session.commit()
|
||||
|
||||
app_was_created.send(app, account=account)
|
||||
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def _append_workflow_export_data(cls, *, export_data: dict, app_model: App, include_secret: bool) -> None:
|
||||
"""
|
||||
Append workflow export data
|
||||
:param export_data: export data
|
||||
:param app_model: App instance
|
||||
"""
|
||||
workflow_service = WorkflowService()
|
||||
workflow = workflow_service.get_draft_workflow(app_model)
|
||||
if not workflow:
|
||||
raise ValueError("Missing draft workflow configuration, please check.")
|
||||
|
||||
export_data['workflow'] = workflow.to_dict(include_secret=include_secret)
|
||||
|
||||
@classmethod
|
||||
def _append_model_config_export_data(cls, export_data: dict, app_model: App) -> None:
|
||||
"""
|
||||
Append model config export data
|
||||
:param export_data: export data
|
||||
:param app_model: App instance
|
||||
"""
|
||||
app_model_config = app_model.app_model_config
|
||||
if not app_model_config:
|
||||
raise ValueError("Missing app configuration, please check.")
|
||||
|
||||
export_data['model_config'] = app_model_config.to_dict()
|
||||
@ -21,7 +21,7 @@ class AppGenerateService:
|
||||
args: Any,
|
||||
invoke_from: InvokeFrom,
|
||||
streaming: bool = True,
|
||||
) -> Union[dict, Generator[dict, None, None]]:
|
||||
):
|
||||
"""
|
||||
App Content Generate
|
||||
:param app_model: app model
|
||||
|
||||
@ -3,7 +3,6 @@ import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import cast
|
||||
|
||||
import yaml
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.pagination import Pagination
|
||||
|
||||
@ -17,13 +16,12 @@ from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelTy
|
||||
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.tools.utils.configuration import ToolParameterConfigurationManager
|
||||
from events.app_event import app_model_config_was_updated, app_was_created
|
||||
from events.app_event import app_was_created
|
||||
from extensions.ext_database import db
|
||||
from models.account import Account
|
||||
from models.model import App, AppMode, AppModelConfig
|
||||
from models.tools import ApiToolProvider
|
||||
from services.tag_service import TagService
|
||||
from services.workflow_service import WorkflowService
|
||||
from tasks.remove_app_and_related_data_task import remove_app_and_related_data_task
|
||||
|
||||
|
||||
@ -100,7 +98,7 @@ class AppService:
|
||||
model_instance = None
|
||||
|
||||
if model_instance:
|
||||
if model_instance.model == default_model_config['model']['name']:
|
||||
if model_instance.model == default_model_config['model']['name'] and model_instance.provider == default_model_config['model']['provider']:
|
||||
default_model_dict = default_model_config['model']
|
||||
else:
|
||||
llm_model = cast(LargeLanguageModel, model_instance.model_type_instance)
|
||||
@ -144,120 +142,6 @@ class AppService:
|
||||
|
||||
return app
|
||||
|
||||
def import_app(self, tenant_id: str, data: str, args: dict, account: Account) -> App:
|
||||
"""
|
||||
Import app
|
||||
:param tenant_id: tenant id
|
||||
:param data: import data
|
||||
:param args: request args
|
||||
:param account: Account instance
|
||||
"""
|
||||
try:
|
||||
import_data = yaml.safe_load(data)
|
||||
except yaml.YAMLError as e:
|
||||
raise ValueError("Invalid YAML format in data argument.")
|
||||
|
||||
app_data = import_data.get('app')
|
||||
model_config_data = import_data.get('model_config')
|
||||
workflow = import_data.get('workflow')
|
||||
|
||||
if not app_data:
|
||||
raise ValueError("Missing app in data argument")
|
||||
|
||||
app_mode = AppMode.value_of(app_data.get('mode'))
|
||||
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
if not workflow:
|
||||
raise ValueError("Missing workflow in data argument "
|
||||
"when app mode is advanced-chat or workflow")
|
||||
elif app_mode in [AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.COMPLETION]:
|
||||
if not model_config_data:
|
||||
raise ValueError("Missing model_config in data argument "
|
||||
"when app mode is chat, agent-chat or completion")
|
||||
else:
|
||||
raise ValueError("Invalid app mode")
|
||||
|
||||
app = App(
|
||||
tenant_id=tenant_id,
|
||||
mode=app_data.get('mode'),
|
||||
name=args.get("name") if args.get("name") else app_data.get('name'),
|
||||
description=args.get("description") if args.get("description") else app_data.get('description', ''),
|
||||
icon=args.get("icon") if args.get("icon") else app_data.get('icon'),
|
||||
icon_background=args.get("icon_background") if args.get("icon_background") \
|
||||
else app_data.get('icon_background'),
|
||||
enable_site=True,
|
||||
enable_api=True
|
||||
)
|
||||
|
||||
db.session.add(app)
|
||||
db.session.commit()
|
||||
|
||||
app_was_created.send(app, account=account)
|
||||
|
||||
if workflow:
|
||||
# init draft workflow
|
||||
workflow_service = WorkflowService()
|
||||
draft_workflow = workflow_service.sync_draft_workflow(
|
||||
app_model=app,
|
||||
graph=workflow.get('graph'),
|
||||
features=workflow.get('features'),
|
||||
unique_hash=None,
|
||||
account=account
|
||||
)
|
||||
workflow_service.publish_workflow(
|
||||
app_model=app,
|
||||
account=account,
|
||||
draft_workflow=draft_workflow
|
||||
)
|
||||
|
||||
if model_config_data:
|
||||
app_model_config = AppModelConfig()
|
||||
app_model_config = app_model_config.from_model_config_dict(model_config_data)
|
||||
app_model_config.app_id = app.id
|
||||
|
||||
db.session.add(app_model_config)
|
||||
db.session.commit()
|
||||
|
||||
app.app_model_config_id = app_model_config.id
|
||||
|
||||
app_model_config_was_updated.send(
|
||||
app,
|
||||
app_model_config=app_model_config
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
def export_app(self, app: App) -> str:
|
||||
"""
|
||||
Export app
|
||||
:param app: App instance
|
||||
:return:
|
||||
"""
|
||||
app_mode = AppMode.value_of(app.mode)
|
||||
|
||||
export_data = {
|
||||
"app": {
|
||||
"name": app.name,
|
||||
"mode": app.mode,
|
||||
"icon": app.icon,
|
||||
"icon_background": app.icon_background,
|
||||
"description": app.description
|
||||
}
|
||||
}
|
||||
|
||||
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
workflow_service = WorkflowService()
|
||||
workflow = workflow_service.get_draft_workflow(app)
|
||||
export_data['workflow'] = {
|
||||
"graph": workflow.graph_dict,
|
||||
"features": workflow.features_dict
|
||||
}
|
||||
else:
|
||||
app_model_config = app.app_model_config
|
||||
|
||||
export_data['model_config'] = app_model_config.to_dict()
|
||||
|
||||
return yaml.dump(export_data)
|
||||
|
||||
def get_app(self, app: App) -> App:
|
||||
"""
|
||||
Get App
|
||||
@ -403,8 +287,12 @@ class AppService:
|
||||
"""
|
||||
db.session.delete(app)
|
||||
db.session.commit()
|
||||
|
||||
# Trigger asynchronous deletion of app and related data
|
||||
remove_app_and_related_data_task.delay(app.id)
|
||||
remove_app_and_related_data_task.delay(
|
||||
tenant_id=app.tenant_id,
|
||||
app_id=app.id
|
||||
)
|
||||
|
||||
def get_app_meta(self, app_model: App) -> dict:
|
||||
"""
|
||||
@ -462,7 +350,7 @@ class AppService:
|
||||
try:
|
||||
provider: ApiToolProvider = db.session.query(ApiToolProvider).filter(
|
||||
ApiToolProvider.id == provider_id
|
||||
)
|
||||
).first()
|
||||
meta['tool_icons'][tool_name] = json.loads(provider.icon)
|
||||
except:
|
||||
meta['tool_icons'][tool_name] = {
|
||||
|
||||
@ -524,7 +524,14 @@ class DocumentService:
|
||||
@staticmethod
|
||||
def delete_document(document):
|
||||
# trigger document_was_deleted signal
|
||||
document_was_deleted.send(document.id, dataset_id=document.dataset_id, doc_form=document.doc_form)
|
||||
file_id = None
|
||||
if document.data_source_type == 'upload_file':
|
||||
if document.data_source_info:
|
||||
data_source_info = document.data_source_info_dict
|
||||
if data_source_info and 'upload_file_id' in data_source_info:
|
||||
file_id = data_source_info['upload_file_id']
|
||||
document_was_deleted.send(document.id, dataset_id=document.dataset_id,
|
||||
doc_form=document.doc_form, file_id=file_id)
|
||||
|
||||
db.session.delete(document)
|
||||
db.session.commit()
|
||||
@ -681,7 +688,7 @@ class DocumentService:
|
||||
dataset.collection_binding_id = dataset_collection_binding.id
|
||||
if not dataset.retrieval_model:
|
||||
default_retrieval_model = {
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH,
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH.value,
|
||||
'reranking_enable': False,
|
||||
'reranking_model': {
|
||||
'reranking_provider_name': '',
|
||||
@ -838,13 +845,17 @@ class DocumentService:
|
||||
'only_main_content': website_info.get('only_main_content', False),
|
||||
'mode': 'crawl',
|
||||
}
|
||||
if len(url) > 255:
|
||||
document_name = url[:200] + '...'
|
||||
else:
|
||||
document_name = url
|
||||
document = DocumentService.build_document(
|
||||
dataset, dataset_process_rule.id,
|
||||
document_data["data_source"]["type"],
|
||||
document_data["doc_form"],
|
||||
document_data["doc_language"],
|
||||
data_source_info, created_from, position,
|
||||
account, url, batch
|
||||
account, document_name, batch
|
||||
)
|
||||
db.session.add(document)
|
||||
db.session.flush()
|
||||
@ -1052,7 +1063,7 @@ class DocumentService:
|
||||
retrieval_model = document_data['retrieval_model']
|
||||
else:
|
||||
default_retrieval_model = {
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH,
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH.value,
|
||||
'reranking_enable': False,
|
||||
'reranking_model': {
|
||||
'reranking_provider_name': '',
|
||||
|
||||
@ -9,7 +9,7 @@ from models.account import Account
|
||||
from models.dataset import Dataset, DatasetQuery, DocumentSegment
|
||||
|
||||
default_retrieval_model = {
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH,
|
||||
'search_method': RetrievalMethod.SEMANTIC_SEARCH.value,
|
||||
'reranking_enable': False,
|
||||
'reranking_model': {
|
||||
'reranking_provider_name': '',
|
||||
@ -38,14 +38,16 @@ class HitTestingService:
|
||||
if not retrieval_model:
|
||||
retrieval_model = dataset.retrieval_model if dataset.retrieval_model else default_retrieval_model
|
||||
|
||||
all_documents = RetrievalService.retrieve(retrival_method=retrieval_model['search_method'],
|
||||
all_documents = RetrievalService.retrieve(retrival_method=retrieval_model.get('search_method', 'semantic_search'),
|
||||
dataset_id=dataset.id,
|
||||
query=query,
|
||||
top_k=retrieval_model['top_k'],
|
||||
query=cls.escape_query_for_search(query),
|
||||
top_k=retrieval_model.get('top_k', 2),
|
||||
score_threshold=retrieval_model['score_threshold']
|
||||
if retrieval_model['score_threshold_enabled'] else None,
|
||||
reranking_model=retrieval_model['reranking_model']
|
||||
if retrieval_model['reranking_enable'] else None
|
||||
if retrieval_model['reranking_enable'] else None,
|
||||
reranking_mode=retrieval_model.get('reranking_mode', None),
|
||||
weights=retrieval_model.get('weights', None),
|
||||
)
|
||||
|
||||
end = time.perf_counter()
|
||||
@ -104,3 +106,7 @@ class HitTestingService:
|
||||
|
||||
if not query or len(query) > 250:
|
||||
raise ValueError('Query is required and cannot exceed 250 characters')
|
||||
|
||||
@staticmethod
|
||||
def escape_query_for_search(query: str) -> str:
|
||||
return query.replace('"', '\\"')
|
||||
|
||||
@ -131,7 +131,7 @@ class ModelLoadBalancingService:
|
||||
load_balancing_configs.insert(0, inherit_config)
|
||||
else:
|
||||
# move the inherit configuration to the first
|
||||
for i, load_balancing_config in enumerate(load_balancing_configs):
|
||||
for i, load_balancing_config in enumerate(load_balancing_configs[:]):
|
||||
if load_balancing_config.name == '__inherit__':
|
||||
inherit_config = load_balancing_configs.pop(i)
|
||||
load_balancing_configs.insert(0, inherit_config)
|
||||
|
||||
@ -4,12 +4,13 @@ from os import path
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from flask import current_app
|
||||
|
||||
from configs import dify_config
|
||||
from constants.languages import languages
|
||||
from extensions.ext_database import db
|
||||
from models.model import App, RecommendedApp
|
||||
from services.app_service import AppService
|
||||
from services.app_dsl_service import AppDslService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -186,16 +187,13 @@ class RecommendedAppService:
|
||||
if not app_model or not app_model.is_public:
|
||||
return None
|
||||
|
||||
app_service = AppService()
|
||||
export_str = app_service.export_app(app_model)
|
||||
|
||||
return {
|
||||
'id': app_model.id,
|
||||
'name': app_model.name,
|
||||
'icon': app_model.icon,
|
||||
'icon_background': app_model.icon_background,
|
||||
'mode': app_model.mode,
|
||||
'export_data': export_str
|
||||
'export_data': AppDslService.export_dsl(app_model=app_model)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -199,7 +199,8 @@ class WorkflowConverter:
|
||||
version='draft',
|
||||
graph=json.dumps(graph),
|
||||
features=json.dumps(features),
|
||||
created_by=account_id
|
||||
created_by=account_id,
|
||||
environment_variables=[],
|
||||
)
|
||||
|
||||
db.session.add(workflow)
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import json
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
import yaml
|
||||
|
||||
from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager
|
||||
from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager
|
||||
from core.app.segments import Variable
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.workflow.entities.node_entities import NodeType
|
||||
from core.workflow.errors import WorkflowNodeRunFailedError
|
||||
@ -63,11 +63,16 @@ class WorkflowService:
|
||||
|
||||
return workflow
|
||||
|
||||
def sync_draft_workflow(self, app_model: App,
|
||||
graph: dict,
|
||||
features: dict,
|
||||
unique_hash: Optional[str],
|
||||
account: Account) -> Workflow:
|
||||
def sync_draft_workflow(
|
||||
self,
|
||||
*,
|
||||
app_model: App,
|
||||
graph: dict,
|
||||
features: dict,
|
||||
unique_hash: Optional[str],
|
||||
account: Account,
|
||||
environment_variables: Sequence[Variable],
|
||||
) -> Workflow:
|
||||
"""
|
||||
Sync draft workflow
|
||||
:raises WorkflowHashNotEqualError
|
||||
@ -75,10 +80,8 @@ class WorkflowService:
|
||||
# fetch draft workflow by app_model
|
||||
workflow = self.get_draft_workflow(app_model=app_model)
|
||||
|
||||
if workflow:
|
||||
# validate unique hash
|
||||
if workflow.unique_hash != unique_hash:
|
||||
raise WorkflowHashNotEqualError()
|
||||
if workflow and workflow.unique_hash != unique_hash:
|
||||
raise WorkflowHashNotEqualError()
|
||||
|
||||
# validate features structure
|
||||
self.validate_features_structure(
|
||||
@ -95,7 +98,8 @@ class WorkflowService:
|
||||
version='draft',
|
||||
graph=json.dumps(graph),
|
||||
features=json.dumps(features),
|
||||
created_by=account.id
|
||||
created_by=account.id,
|
||||
environment_variables=environment_variables
|
||||
)
|
||||
db.session.add(workflow)
|
||||
# update draft workflow if found
|
||||
@ -104,6 +108,7 @@ class WorkflowService:
|
||||
workflow.features = json.dumps(features)
|
||||
workflow.updated_by = account.id
|
||||
workflow.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
workflow.environment_variables = environment_variables
|
||||
|
||||
# commit db session changes
|
||||
db.session.commit()
|
||||
@ -114,56 +119,6 @@ class WorkflowService:
|
||||
# return draft workflow
|
||||
return workflow
|
||||
|
||||
def import_draft_workflow(self, app_model: App,
|
||||
data: str,
|
||||
account: Account) -> Workflow:
|
||||
"""
|
||||
Import draft workflow
|
||||
:param app_model: App instance
|
||||
:param data: import data
|
||||
:param account: Account instance
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
import_data = yaml.safe_load(data)
|
||||
except yaml.YAMLError as e:
|
||||
raise ValueError("Invalid YAML format in data argument.")
|
||||
|
||||
app_data = import_data.get('app')
|
||||
workflow = import_data.get('workflow')
|
||||
|
||||
if not app_data:
|
||||
raise ValueError("Missing app in data argument")
|
||||
|
||||
app_mode = AppMode.value_of(app_data.get('mode'))
|
||||
if app_mode not in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
|
||||
raise ValueError("Only support import workflow in advanced-chat or workflow app.")
|
||||
|
||||
if app_data.get('mode') != app_model.mode:
|
||||
raise ValueError(f"App mode {app_data.get('mode')} is not matched with current app mode {app_model.mode}")
|
||||
|
||||
if not workflow:
|
||||
raise ValueError("Missing workflow in data argument "
|
||||
"when app mode is advanced-chat or workflow")
|
||||
|
||||
# fetch draft workflow by app_model
|
||||
current_draft_workflow = self.get_draft_workflow(app_model=app_model)
|
||||
if current_draft_workflow:
|
||||
unique_hash = current_draft_workflow.unique_hash
|
||||
else:
|
||||
unique_hash = None
|
||||
|
||||
# sync draft workflow
|
||||
draft_workflow = self.sync_draft_workflow(
|
||||
app_model=app_model,
|
||||
graph=workflow.get('graph'),
|
||||
features=workflow.get('features'),
|
||||
unique_hash=unique_hash,
|
||||
account=account
|
||||
)
|
||||
|
||||
return draft_workflow
|
||||
|
||||
def publish_workflow(self, app_model: App,
|
||||
account: Account,
|
||||
draft_workflow: Optional[Workflow] = None) -> Workflow:
|
||||
@ -189,7 +144,8 @@ class WorkflowService:
|
||||
version=str(datetime.now(timezone.utc).replace(tzinfo=None)),
|
||||
graph=draft_workflow.graph,
|
||||
features=draft_workflow.features,
|
||||
created_by=account.id
|
||||
created_by=account.id,
|
||||
environment_variables=draft_workflow.environment_variables
|
||||
)
|
||||
|
||||
# commit db session changes
|
||||
|
||||
Reference in New Issue
Block a user