Commit 9249c38b authored by takatost's avatar takatost

refactor app api

parent 67e0ba51
import json
from datetime import datetime
from typing import cast
import yaml
from flask_login import current_user from flask_login import current_user
from flask_restful import Resource, abort, inputs, marshal_with, reqparse from flask_restful import Resource, abort, inputs, marshal_with, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden, BadRequest
from constants.model_template import default_app_templates
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.setup import setup_required from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
from core.errors.error import ProviderTokenNotInitError
from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType, ModelPropertyKey
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from events.app_event import app_was_created, app_was_deleted
from extensions.ext_database import db
from fields.app_fields import ( from fields.app_fields import (
app_detail_fields, app_detail_fields,
app_detail_fields_with_site, app_detail_fields_with_site,
app_pagination_fields, app_pagination_fields,
) )
from libs.login import login_required from libs.login import login_required
from services.app_service import AppService
from models.model import App, AppModelConfig, AppMode from models.model import App, AppModelConfig, AppMode
from services.workflow_service import WorkflowService from services.workflow_service import WorkflowService
from core.tools.utils.configuration import ToolParameterConfigurationManager from core.tools.utils.configuration import ToolParameterConfigurationManager
...@@ -49,32 +38,9 @@ class AppListApi(Resource): ...@@ -49,32 +38,9 @@ class AppListApi(Resource):
parser.add_argument('name', type=str, location='args', required=False) parser.add_argument('name', type=str, location='args', required=False)
args = parser.parse_args() args = parser.parse_args()
filters = [ # get app list
App.tenant_id == current_user.current_tenant_id, app_service = AppService()
App.is_universal == False app_models = app_service.get_paginate_apps(current_user.current_tenant_id, args)
]
if args['mode'] == 'workflow':
filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))
elif args['mode'] == 'chat':
filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value]))
elif args['mode'] == 'agent':
filters.append(App.mode == AppMode.AGENT_CHAT.value)
elif args['mode'] == 'channel':
filters.append(App.mode == AppMode.CHANNEL.value)
else:
pass
if 'name' in args and args['name']:
name = args['name'][:30]
filters.append(App.name.ilike(f'%{name}%'))
app_models = db.paginate(
db.select(App).where(*filters).order_by(App.created_at.desc()),
page=args['page'],
per_page=args['limit'],
error_out=False
)
return app_models return app_models
...@@ -97,63 +63,10 @@ class AppListApi(Resource): ...@@ -97,63 +63,10 @@ class AppListApi(Resource):
raise Forbidden() raise Forbidden()
if 'mode' not in args or args['mode'] is None: if 'mode' not in args or args['mode'] is None:
abort(400, message="mode is required") raise BadRequest("mode is required")
app_mode = AppMode.value_of(args['mode'])
app_template = default_app_templates[app_mode]
# get model config
default_model_config = app_template['model_config']
if 'model' in default_model_config:
# get model provider
model_manager = ModelManager()
# get default model instance
try:
model_instance = model_manager.get_default_model_instance(
tenant_id=current_user.current_tenant_id,
model_type=ModelType.LLM
)
except ProviderTokenNotInitError:
model_instance = None
if model_instance:
if model_instance.model == default_model_config['model']['name']:
default_model_dict = default_model_config['model']
else:
llm_model = cast(LargeLanguageModel, model_instance.model_type_instance)
model_schema = llm_model.get_model_schema(model_instance.model, model_instance.credentials)
default_model_dict = {
'provider': model_instance.provider,
'name': model_instance.model,
'mode': model_schema.model_properties.get(ModelPropertyKey.MODE),
'completion_params': {}
}
else:
default_model_dict = default_model_config['model']
default_model_config['model'] = json.dumps(default_model_dict)
app = App(**app_template['app'])
app.name = args['name']
app.mode = args['mode']
app.icon = args['icon']
app.icon_background = args['icon_background']
app.tenant_id = current_user.current_tenant_id
db.session.add(app)
db.session.flush()
app_model_config = AppModelConfig(**default_model_config)
app_model_config.app_id = app.id
db.session.add(app_model_config)
db.session.flush()
app.app_model_config_id = app_model_config.id
app_was_created.send(app, account=current_user) app_service = AppService()
app = app_service.create_app(current_user.current_tenant_id, args, current_user)
return app, 201 return app, 201
...@@ -177,54 +90,8 @@ class AppImportApi(Resource): ...@@ -177,54 +90,8 @@ class AppImportApi(Resource):
parser.add_argument('icon_background', type=str, location='json') parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args() args = parser.parse_args()
try: app_service = AppService()
import_data = yaml.safe_load(args['data']) app = app_service.import_app(current_user.current_tenant_id, args, current_user)
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_graph = import_data.get('workflow_graph')
if not app_data or not model_config_data:
raise ValueError("Missing app or model_config in data argument")
app_mode = AppMode.value_of(app_data.get('mode'))
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
if not workflow_graph:
raise ValueError("Missing workflow_graph in data argument "
"when mode is advanced-chat or workflow")
app = App(
tenant_id=current_user.current_tenant_id,
mode=app_data.get('mode'),
name=args.get("name") if args.get("name") else app_data.get('name'),
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()
if workflow_graph:
workflow_service = WorkflowService()
draft_workflow = workflow_service.sync_draft_workflow(app, workflow_graph, current_user)
published_workflow = workflow_service.publish_draft_workflow(app, current_user, draft_workflow)
model_config_data['workflow_id'] = published_workflow.id
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_was_created.send(app, account=current_user)
return app, 201 return app, 201
...@@ -281,13 +148,8 @@ class AppApi(Resource): ...@@ -281,13 +148,8 @@ class AppApi(Resource):
if not current_user.is_admin_or_owner: if not current_user.is_admin_or_owner:
raise Forbidden() raise Forbidden()
db.session.delete(app_model) app_service = AppService()
db.session.commit() app_service.delete_app(app_model)
# todo delete related data??
# model_config, site, api_token, conversation, message, message_feedback, message_annotation
app_was_deleted.send(app_model)
return {'result': 'success'}, 204 return {'result': 'success'}, 204
...@@ -299,28 +161,10 @@ class AppExportApi(Resource): ...@@ -299,28 +161,10 @@ class AppExportApi(Resource):
@get_app_model @get_app_model
def get(self, app_model): def get(self, app_model):
"""Export app""" """Export app"""
app_model_config = app_model.app_model_config app_service = AppService()
export_data = {
"app": {
"name": app_model.name,
"mode": app_model.mode,
"icon": app_model.icon,
"icon_background": app_model.icon_background
},
"model_config": app_model_config.to_dict(),
}
if app_model_config.workflow_id:
export_data['workflow_graph'] = json.loads(app_model_config.workflow.graph)
else:
# get draft workflow
workflow_service = WorkflowService()
workflow = workflow_service.get_draft_workflow(app_model)
export_data['workflow_graph'] = json.loads(workflow.graph)
return { return {
"data": yaml.dump(export_data) "data": app_service.export_app(app_model)
} }
...@@ -335,9 +179,9 @@ class AppNameApi(Resource): ...@@ -335,9 +179,9 @@ class AppNameApi(Resource):
parser.add_argument('name', type=str, required=True, location='json') parser.add_argument('name', type=str, required=True, location='json')
args = parser.parse_args() args = parser.parse_args()
app_model.name = args.get('name') app_service = AppService()
app_model.updated_at = datetime.utcnow() app_model = app_service.update_app_name(app_model, args.get('name'))
db.session.commit()
return app_model return app_model
...@@ -353,10 +197,8 @@ class AppIconApi(Resource): ...@@ -353,10 +197,8 @@ class AppIconApi(Resource):
parser.add_argument('icon_background', type=str, location='json') parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args() args = parser.parse_args()
app_model.icon = args.get('icon') app_service = AppService()
app_model.icon_background = args.get('icon_background') app_model = app_service.update_app_icon(app_model, args.get('icon'), args.get('icon_background'))
app_model.updated_at = datetime.utcnow()
db.session.commit()
return app_model return app_model
...@@ -372,12 +214,9 @@ class AppSiteStatus(Resource): ...@@ -372,12 +214,9 @@ class AppSiteStatus(Resource):
parser.add_argument('enable_site', type=bool, required=True, location='json') parser.add_argument('enable_site', type=bool, required=True, location='json')
args = parser.parse_args() args = parser.parse_args()
if args.get('enable_site') == app_model.enable_site: app_service = AppService()
return app_model app_model = app_service.update_app_site_status(app_model, args.get('enable_site'))
app_model.enable_site = args.get('enable_site')
app_model.updated_at = datetime.utcnow()
db.session.commit()
return app_model return app_model
...@@ -392,12 +231,9 @@ class AppApiStatus(Resource): ...@@ -392,12 +231,9 @@ class AppApiStatus(Resource):
parser.add_argument('enable_api', type=bool, required=True, location='json') parser.add_argument('enable_api', type=bool, required=True, location='json')
args = parser.parse_args() args = parser.parse_args()
if args.get('enable_api') == app_model.enable_api: app_service = AppService()
return app_model app_model = app_service.update_app_api_status(app_model, args.get('enable_api'))
app_model.enable_api = args.get('enable_api')
app_model.updated_at = datetime.utcnow()
db.session.commit()
return app_model return app_model
......
import json
import yaml
from flask_login import current_user from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse from flask_restful import Resource, fields, marshal_with, reqparse
...@@ -9,7 +6,7 @@ from controllers.console import api ...@@ -9,7 +6,7 @@ from controllers.console import api
from controllers.console.app.error import AppNotFoundError from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db from extensions.ext_database import db
from models.model import App, RecommendedApp from models.model import App, RecommendedApp
from services.workflow_service import WorkflowService from services.app_service import AppService
app_fields = { app_fields = {
'id': fields.String, 'id': fields.String,
...@@ -103,25 +100,8 @@ class RecommendedAppApi(Resource): ...@@ -103,25 +100,8 @@ class RecommendedAppApi(Resource):
if not app_model or not app_model.is_public: if not app_model or not app_model.is_public:
raise AppNotFoundError raise AppNotFoundError
app_model_config = app_model.app_model_config app_service = AppService()
export_str = app_service.export_app(app_model)
export_data = {
"app": {
"name": app_model.name,
"mode": app_model.mode,
"icon": app_model.icon,
"icon_background": app_model.icon_background
},
"model_config": app_model_config.to_dict(),
}
if app_model_config.workflow_id:
export_data['workflow_graph'] = json.loads(app_model_config.workflow.graph)
else:
# get draft workflow
workflow_service = WorkflowService()
workflow = workflow_service.get_draft_workflow(app_model)
export_data['workflow_graph'] = json.loads(workflow.graph)
return { return {
'id': app_model.id, 'id': app_model.id,
...@@ -129,7 +109,7 @@ class RecommendedAppApi(Resource): ...@@ -129,7 +109,7 @@ class RecommendedAppApi(Resource):
'icon': app_model.icon, 'icon': app_model.icon,
'icon_background': app_model.icon_background, 'icon_background': app_model.icon_background,
'mode': app_model.mode, 'mode': app_model.mode,
'export_data': yaml.dump(export_data) 'export_data': export_str
} }
......
import json
from datetime import datetime
from typing import cast
import yaml
from constants.model_template import default_app_templates
from core.errors.error import ProviderTokenNotInitError
from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType, ModelPropertyKey
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from events.app_event import app_was_created, app_was_deleted
from extensions.ext_database import db
from models.account import Account
from models.model import App, AppMode, AppModelConfig
from services.workflow_service import WorkflowService
class AppService:
def get_paginate_apps(self, tenant_id: str, args: dict) -> list[App]:
"""
Get app list with pagination
:param tenant_id: tenant id
:param args: request args
:return:
"""
filters = [
App.tenant_id == tenant_id,
App.is_universal == False
]
if args['mode'] == 'workflow':
filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))
elif args['mode'] == 'chat':
filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value]))
elif args['mode'] == 'agent':
filters.append(App.mode == AppMode.AGENT_CHAT.value)
elif args['mode'] == 'channel':
filters.append(App.mode == AppMode.CHANNEL.value)
if 'name' in args and args['name']:
name = args['name'][:30]
filters.append(App.name.ilike(f'%{name}%'))
app_models = db.paginate(
db.select(App).where(*filters).order_by(App.created_at.desc()),
page=args['page'],
per_page=args['limit'],
error_out=False
)
return app_models
def create_app(self, tenant_id: str, args: dict, account: Account) -> App:
"""
Create app
:param tenant_id: tenant id
:param args: request args
:param account: Account instance
"""
app_mode = AppMode.value_of(args['mode'])
app_template = default_app_templates[app_mode]
# get model config
default_model_config = app_template['model_config']
if 'model' in default_model_config:
# get model provider
model_manager = ModelManager()
# get default model instance
try:
model_instance = model_manager.get_default_model_instance(
tenant_id=account.current_tenant_id,
model_type=ModelType.LLM
)
except ProviderTokenNotInitError:
model_instance = None
if model_instance:
if model_instance.model == default_model_config['model']['name']:
default_model_dict = default_model_config['model']
else:
llm_model = cast(LargeLanguageModel, model_instance.model_type_instance)
model_schema = llm_model.get_model_schema(model_instance.model, model_instance.credentials)
default_model_dict = {
'provider': model_instance.provider,
'name': model_instance.model,
'mode': model_schema.model_properties.get(ModelPropertyKey.MODE),
'completion_params': {}
}
else:
default_model_dict = default_model_config['model']
default_model_config['model'] = json.dumps(default_model_dict)
app = App(**app_template['app'])
app.name = args['name']
app.mode = args['mode']
app.icon = args['icon']
app.icon_background = args['icon_background']
app.tenant_id = account.current_tenant_id
db.session.add(app)
db.session.flush()
app_model_config = AppModelConfig(**default_model_config)
app_model_config.app_id = app.id
db.session.add(app_model_config)
db.session.flush()
app.app_model_config_id = app_model_config.id
app_was_created.send(app, account=account)
return app
def import_app(self, tenant_id: str, args: dict, account: Account) -> App:
"""
Import app
:param tenant_id: tenant id
:param args: request args
:param account: Account instance
"""
try:
import_data = yaml.safe_load(args['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_graph = import_data.get('workflow_graph')
if not app_data or not model_config_data:
raise ValueError("Missing app or model_config in data argument")
app_mode = AppMode.value_of(app_data.get('mode'))
if app_mode in [AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]:
if not workflow_graph:
raise ValueError("Missing workflow_graph in data argument "
"when mode is advanced-chat or workflow")
app = App(
tenant_id=tenant_id,
mode=app_data.get('mode'),
name=args.get("name") if args.get("name") else app_data.get('name'),
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()
if workflow_graph:
workflow_service = WorkflowService()
draft_workflow = workflow_service.sync_draft_workflow(app, workflow_graph, account)
published_workflow = workflow_service.publish_draft_workflow(app, account, draft_workflow)
model_config_data['workflow_id'] = published_workflow.id
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_was_created.send(app, account=account)
return app
def export_app(self, app: App) -> str:
"""
Export app
:param app: App instance
:return:
"""
app_model_config = app.app_model_config
export_data = {
"app": {
"name": app.name,
"mode": app.mode,
"icon": app.icon,
"icon_background": app.icon_background
},
"model_config": app_model_config.to_dict(),
}
if app_model_config.workflow_id:
export_data['workflow_graph'] = json.loads(app_model_config.workflow.graph)
else:
workflow_service = WorkflowService()
workflow = workflow_service.get_draft_workflow(app)
export_data['workflow_graph'] = json.loads(workflow.graph)
return yaml.dump(export_data)
def update_app_name(self, app: App, name: str) -> App:
"""
Update app name
:param app: App instance
:param name: new name
:return: App instance
"""
app.name = name
app.updated_at = datetime.utcnow()
db.session.commit()
return app
def update_app_icon(self, app: App, icon: str, icon_background: str) -> App:
"""
Update app icon
:param app: App instance
:param icon: new icon
:param icon_background: new icon_background
:return: App instance
"""
app.icon = icon
app.icon_background = icon_background
app.updated_at = datetime.utcnow()
db.session.commit()
return app
def update_app_site_status(self, app: App, enable_site: bool) -> App:
"""
Update app site status
:param app: App instance
:param enable_site: enable site status
:return: App instance
"""
if enable_site == app.enable_site:
return app
app.enable_site = enable_site
app.updated_at = datetime.utcnow()
db.session.commit()
return app
def update_app_api_status(self, app: App, enable_api: bool) -> App:
"""
Update app api status
:param app: App instance
:param enable_api: enable api status
:return: App instance
"""
if enable_api == app.enable_api:
return app
app.enable_api = enable_api
app.updated_at = datetime.utcnow()
db.session.commit()
return app
def delete_app(self, app: App) -> None:
"""
Delete app
:param app: App instance
"""
db.session.delete(app)
db.session.commit()
app_was_deleted.send(app)
# todo async delete related data by event
# app_model_configs, site, api_tokens, installed_apps, recommended_apps BY app
# app_annotation_hit_histories, app_annotation_settings, app_dataset_joins BY app
# workflows, workflow_runs, workflow_node_executions, workflow_app_logs BY app
# conversations, pinned_conversations, messages BY app
# message_feedbacks, message_annotations, message_chains BY message
# message_agent_thoughts, message_files, saved_messages BY message
...@@ -21,7 +21,7 @@ from events.app_event import app_was_created ...@@ -21,7 +21,7 @@ from events.app_event import app_was_created
from extensions.ext_database import db from extensions.ext_database import db
from models.account import Account from models.account import Account
from models.api_based_extension import APIBasedExtension, APIBasedExtensionPoint from models.api_based_extension import APIBasedExtension, APIBasedExtensionPoint
from models.model import App, AppMode, AppModelConfig, Site from models.model import App, AppMode, AppModelConfig
from models.workflow import Workflow, WorkflowType from models.workflow import Workflow, WorkflowType
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment