Commit 116e02ec authored by takatost's avatar takatost

refactor app mode

add app import and export
parent 62797bca
...@@ -26,439 +26,3 @@ def supported_language(lang): ...@@ -26,439 +26,3 @@ def supported_language(lang):
error = ('{lang} is not a valid language.' error = ('{lang} is not a valid language.'
.format(lang=lang)) .format(lang=lang))
raise ValueError(error) raise ValueError(error)
user_input_form_template = {
"en-US": [
{
"paragraph": {
"label": "Query",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"zh-Hans": [
{
"paragraph": {
"label": "查询内容",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"pt-BR": [
{
"paragraph": {
"label": "Consulta",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"es-ES": [
{
"paragraph": {
"label": "Consulta",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
"ua-UK": [
{
"paragraph": {
"label": "Запит",
"variable": "default_input",
"required": False,
"default": ""
}
}
],
}
demo_model_templates = {
'en-US': [
{
'name': 'Translation Assistant',
'icon': '',
'icon_background': '',
'description': 'A multilingual translator that provides translation capabilities in multiple languages, translating user input into the language they need.',
'mode': 'completion',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo-instruct',
configs={
'prompt_template': "Please translate the following text into {{target_language}}:\n",
'prompt_variables': [
{
"key": "target_language",
"name": "Target Language",
"description": "The language you want to translate into.",
"type": "select",
"default": "Chinese",
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
],
'completion_params': {
'max_token': 1000,
'temperature': 0,
'top_p': 0,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='',
suggested_questions=None,
pre_prompt="Please translate the following text into {{target_language}}:\n{{query}}\ntranslate:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=json.dumps([
{
"select": {
"label": "Target Language",
"variable": "target_language",
"description": "The language you want to translate into.",
"default": "Chinese",
"required": True,
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
}, {
"paragraph": {
"label": "Query",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
'name': 'AI Front-end Interviewer',
'icon': '',
'icon_background': '',
'description': 'A simulated front-end interviewer that tests the skill level of front-end development through questioning.',
'mode': 'chat',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo',
configs={
'introduction': 'Hi, welcome to our interview. I am the interviewer for this technology company, and I will test your web front-end development skills. Next, I will ask you some technical questions. Please answer them as thoroughly as possible. ',
'prompt_template': "You will play the role of an interviewer for a technology company, examining the user's web front-end development skills and posing 5-10 sharp technical questions.\n\nPlease note:\n- Only ask one question at a time.\n- After the user answers a question, ask the next question directly, without trying to correct any mistakes made by the candidate.\n- If you think the user has not answered correctly for several consecutive questions, ask fewer questions.\n- After asking the last question, you can ask this question: Why did you leave your last job? After the user answers this question, please express your understanding and support.\n",
'prompt_variables': [],
'completion_params': {
'max_token': 300,
'temperature': 0.8,
'top_p': 0.9,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='Hi, welcome to our interview. I am the interviewer for this technology company, and I will test your web front-end development skills. Next, I will ask you some technical questions. Please answer them as thoroughly as possible. ',
suggested_questions=None,
pre_prompt="You will play the role of an interviewer for a technology company, examining the user's web front-end development skills and posing 5-10 sharp technical questions.\n\nPlease note:\n- Only ask one question at a time.\n- After the user answers a question, ask the next question directly, without trying to correct any mistakes made by the candidate.\n- If you think the user has not answered correctly for several consecutive questions, ask fewer questions.\n- After asking the last question, you can ask this question: Why did you leave your last job? After the user answers this question, please express your understanding and support.\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=None
)
}
],
'zh-Hans': [
{
'name': '翻译助手',
'icon': '',
'icon_background': '',
'description': '一个多语言翻译器,提供多种语言翻译能力,将用户输入的文本翻译成他们需要的语言。',
'mode': 'completion',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo-instruct',
configs={
'prompt_template': "请将以下文本翻译为{{target_language}}:\n",
'prompt_variables': [
{
"key": "target_language",
"name": "目标语言",
"description": "翻译的目标语言",
"type": "select",
"default": "中文",
"options": [
"中文",
"英文",
"日语",
"法语",
"俄语",
"德语",
"西班牙语",
"韩语",
"意大利语",
]
}
],
'completion_params': {
'max_token': 1000,
'temperature': 0,
'top_p': 0,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='',
suggested_questions=None,
pre_prompt="请将以下文本翻译为{{target_language}}:\n{{query}}\n翻译:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=json.dumps([
{
"select": {
"label": "目标语言",
"variable": "target_language",
"description": "翻译的目标语言",
"default": "中文",
"required": True,
'options': [
"中文",
"英文",
"日语",
"法语",
"俄语",
"德语",
"西班牙语",
"韩语",
"意大利语",
]
}
}, {
"paragraph": {
"label": "文本内容",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
'name': 'AI 前端面试官',
'icon': '',
'icon_background': '',
'description': '一个模拟的前端面试官,通过提问的方式对前端开发的技能水平进行检验。',
'mode': 'chat',
'model_config': AppModelConfig(
provider='openai',
model_id='gpt-3.5-turbo',
configs={
'introduction': '你好,欢迎来参加我们的面试,我是这家科技公司的面试官,我将考察你的 Web 前端开发技能。接下来我会向您提出一些技术问题,请您尽可能详尽地回答。',
'prompt_template': "你将扮演一个科技公司的面试官,考察用户作为候选人的 Web 前端开发水平,提出 5-10 个犀利的技术问题。\n\n请注意:\n- 每次只问一个问题\n- 用户回答问题后请直接问下一个问题,而不要试图纠正候选人的错误;\n- 如果你认为用户连续几次回答的都不对,就少问一点;\n- 问完最后一个问题后,你可以问这样一个问题:上一份工作为什么离职?用户回答该问题后,请表示理解与支持。\n",
'prompt_variables': [],
'completion_params': {
'max_token': 300,
'temperature': 0.8,
'top_p': 0.9,
'presence_penalty': 0.1,
'frequency_penalty': 0.1,
}
},
opening_statement='你好,欢迎来参加我们的面试,我是这家科技公司的面试官,我将考察你的 Web 前端开发技能。接下来我会向您提出一些技术问题,请您尽可能详尽地回答。',
suggested_questions=None,
pre_prompt="你将扮演一个科技公司的面试官,考察用户作为候选人的 Web 前端开发水平,提出 5-10 个犀利的技术问题。\n\n请注意:\n- 每次只问一个问题\n- 用户回答问题后请直接问下一个问题,而不要试图纠正候选人的错误;\n- 如果你认为用户连续几次回答的都不对,就少问一点;\n- 问完最后一个问题后,你可以问这样一个问题:上一份工作为什么离职?用户回答该问题后,请表示理解与支持。\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1
}
}),
user_input_form=None
)
}
],
'uk-UA': [{
"name": "Помічник перекладу",
"icon": "",
"icon_background": "",
"description": "Багатомовний перекладач, який надає можливості перекладу різними мовами, перекладаючи введені користувачем дані на потрібну мову.",
"mode": "completion",
"model_config": AppModelConfig(
provider="openai",
model_id="gpt-3.5-turbo-instruct",
configs={
"prompt_template": "Будь ласка, перекладіть наступний текст на {{target_language}}:\n",
"prompt_variables": [
{
"key": "target_language",
"name": "Цільова мова",
"description": "Мова, на яку ви хочете перекласти.",
"type": "select",
"default": "Ukrainian",
"options": [
"Chinese",
"English",
"Japanese",
"French",
"Russian",
"German",
"Spanish",
"Korean",
"Italian",
],
},
],
"completion_params": {
"max_token": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
},
opening_statement="",
suggested_questions=None,
pre_prompt="Будь ласка, перекладіть наступний текст на {{target_language}}:\n{{query}}\ntranslate:",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo-instruct",
"mode": "completion",
"completion_params": {
"max_tokens": 1000,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
}),
user_input_form=json.dumps([
{
"select": {
"label": "Цільова мова",
"variable": "target_language",
"description": "Мова, на яку ви хочете перекласти.",
"default": "Chinese",
"required": True,
'options': [
'Chinese',
'English',
'Japanese',
'French',
'Russian',
'German',
'Spanish',
'Korean',
'Italian',
]
}
}, {
"paragraph": {
"label": "Запит",
"variable": "query",
"required": True,
"default": ""
}
}
])
)
},
{
"name": "AI інтерв’юер фронтенду",
"icon": "",
"icon_background": "",
"description": "Симульований інтерв’юер фронтенду, який перевіряє рівень кваліфікації у розробці фронтенду через опитування.",
"mode": "chat",
"model_config": AppModelConfig(
provider="openai",
model_id="gpt-3.5-turbo",
configs={
"introduction": "Привіт, ласкаво просимо на наше співбесіду. Я інтерв'юер цієї технологічної компанії, і я перевірю ваші навички веб-розробки фронтенду. Далі я поставлю вам декілька технічних запитань. Будь ласка, відповідайте якомога ретельніше. ",
"prompt_template": "Ви будете грати роль інтерв'юера технологічної компанії, перевіряючи навички розробки фронтенду користувача та ставлячи 5-10 чітких технічних питань.\n\nЗверніть увагу:\n- Ставте лише одне запитання за раз.\n- Після того, як користувач відповість на запитання, ставте наступне запитання безпосередньо, не намагаючись виправити будь-які помилки, допущені кандидатом.\n- Якщо ви вважаєте, що користувач не відповів правильно на кілька питань поспіль, задайте менше запитань.\n- Після того, як ви задали останнє запитання, ви можете поставити таке запитання: Чому ви залишили свою попередню роботу? Після того, як користувач відповість на це питання, висловіть своє розуміння та підтримку.\n",
"prompt_variables": [],
"completion_params": {
"max_token": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
},
opening_statement="Привіт, ласкаво просимо на наше співбесіду. Я інтерв'юер цієї технологічної компанії, і я перевірю ваші навички веб-розробки фронтенду. Далі я поставлю вам декілька технічних запитань. Будь ласка, відповідайте якомога ретельніше. ",
suggested_questions=None,
pre_prompt="Ви будете грати роль інтерв'юера технологічної компанії, перевіряючи навички розробки фронтенду користувача та ставлячи 5-10 чітких технічних питань.\n\nЗверніть увагу:\n- Ставте лише одне запитання за раз.\n- Після того, як користувач відповість на запитання, ставте наступне запитання безпосередньо, не намагаючись виправити будь-які помилки, допущені кандидатом.\n- Якщо ви вважаєте, що користувач не відповів правильно на кілька питань поспіль, задайте менше запитань.\n- Після того, як ви задали останнє запитання, ви можете поставити таке запитання: Чому ви залишили свою попередню роботу? Після того, як користувач відповість на це питання, висловіть своє розуміння та підтримку.\n",
model=json.dumps({
"provider": "openai",
"name": "gpt-3.5-turbo",
"mode": "chat",
"completion_params": {
"max_tokens": 300,
"temperature": 0.8,
"top_p": 0.9,
"presence_penalty": 0.1,
"frequency_penalty": 0.1,
},
}),
user_input_form=None
),
}
],
}
import json from models.model import AppMode
model_templates = { default_app_templates = {
# workflow default mode # workflow default mode
'workflow_default': { AppMode.WORKFLOW: {
'app': { 'app': {
'mode': 'workflow', 'mode': AppMode.WORKFLOW.value,
'enable_site': True, 'enable_site': True,
'enable_api': True, 'enable_api': True
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
}, },
'model_config': { 'model_config': {}
'provider': '',
'model_id': '',
'configs': {}
}
}, },
# chat default mode # chat default mode
'chat_default': { AppMode.CHAT: {
'app': { 'app': {
'mode': 'chat', 'mode': AppMode.CHAT.value,
'enable_site': True, 'enable_site': True,
'enable_api': True, 'enable_api': True
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
}, },
'model_config': { 'model_config': {
'provider': 'openai', 'model': {
'model_id': 'gpt-4',
'configs': {
'prompt_template': '',
'prompt_variables': [],
'completion_params': {
'max_token': 512,
'temperature': 1,
'top_p': 1,
'presence_penalty': 0,
'frequency_penalty': 0,
}
},
'model': json.dumps({
"provider": "openai", "provider": "openai",
"name": "gpt-4", "name": "gpt-4",
"mode": "chat", "mode": "chat",
...@@ -55,36 +30,42 @@ model_templates = { ...@@ -55,36 +30,42 @@ model_templates = {
"presence_penalty": 0, "presence_penalty": 0,
"frequency_penalty": 0 "frequency_penalty": 0
} }
}) }
} }
}, },
# agent default mode # advanced-chat default mode
'agent_default': { AppMode.ADVANCED_CHAT: {
'app': { 'app': {
'mode': 'agent', 'mode': AppMode.ADVANCED_CHAT.value,
'enable_site': True, 'enable_site': True,
'enable_api': True, 'enable_api': True
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
}, },
'model_config': { 'model_config': {
'provider': 'openai', 'model': {
'model_id': 'gpt-4', "provider": "openai",
'configs': { "name": "gpt-4",
'prompt_template': '', "mode": "chat",
'prompt_variables': [], "completion_params": {
'completion_params': { "max_tokens": 512,
'max_token': 512, "temperature": 1,
'temperature': 1, "top_p": 1,
'top_p': 1, "presence_penalty": 0,
'presence_penalty': 0, "frequency_penalty": 0
'frequency_penalty': 0,
} }
}, }
'model': json.dumps({ }
},
# agent-chat default mode
AppMode.AGENT_CHAT: {
'app': {
'mode': AppMode.AGENT_CHAT.value,
'enable_site': True,
'enable_api': True
},
'model_config': {
'model': {
"provider": "openai", "provider": "openai",
"name": "gpt-4", "name": "gpt-4",
"mode": "chat", "mode": "chat",
...@@ -95,7 +76,7 @@ model_templates = { ...@@ -95,7 +76,7 @@ model_templates = {
"presence_penalty": 0, "presence_penalty": 0,
"frequency_penalty": 0 "frequency_penalty": 0
} }
}) }
} }
}, },
} }
......
import json import json
import logging import logging
from datetime import datetime 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
from constants.languages import demo_model_templates, languages from constants.languages import languages
from constants.model_template import model_templates from constants.model_template import default_app_templates
from controllers.console import api from controllers.console import api
from controllers.console.app.error import ProviderNotInitializeError from controllers.console.app.error import ProviderNotInitializeError
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
...@@ -15,7 +17,8 @@ from controllers.console.setup import setup_required ...@@ -15,7 +17,8 @@ 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 LLMBadRequestError, ProviderTokenNotInitError from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
from core.model_manager import ModelManager from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType, ModelPropertyKey
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
from core.provider_manager import ProviderManager from core.provider_manager import ProviderManager
from events.app_event import app_was_created, app_was_deleted from events.app_event import app_was_created, app_was_deleted
from extensions.ext_database import db from extensions.ext_database import db
...@@ -28,6 +31,10 @@ from fields.app_fields import ( ...@@ -28,6 +31,10 @@ from fields.app_fields import (
from libs.login import login_required from libs.login import login_required
from models.model import App, AppModelConfig, Site, AppMode from models.model import App, AppModelConfig, Site, AppMode
from services.app_model_config_service import AppModelConfigService from services.app_model_config_service import AppModelConfigService
from services.workflow_service import WorkflowService
ALLOW_CREATE_APP_MODES = ['chat', 'agent-chat', 'advanced-chat', 'workflow']
class AppListApi(Resource): class AppListApi(Resource):
...@@ -41,7 +48,7 @@ class AppListApi(Resource): ...@@ -41,7 +48,7 @@ class AppListApi(Resource):
parser = reqparse.RequestParser() parser = reqparse.RequestParser()
parser.add_argument('page', type=inputs.int_range(1, 99999), required=False, default=1, location='args') parser.add_argument('page', type=inputs.int_range(1, 99999), required=False, default=1, location='args')
parser.add_argument('limit', type=inputs.int_range(1, 100), required=False, default=20, location='args') parser.add_argument('limit', type=inputs.int_range(1, 100), required=False, default=20, location='args')
parser.add_argument('mode', type=str, choices=['chat', 'completion', 'all'], default='all', location='args', required=False) parser.add_argument('mode', type=str, choices=['chat', 'workflow', 'agent', 'channel', 'all'], default='all', location='args', required=False)
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()
...@@ -50,15 +57,20 @@ class AppListApi(Resource): ...@@ -50,15 +57,20 @@ class AppListApi(Resource):
App.is_universal == False App.is_universal == False
] ]
if args['mode'] == 'completion': if args['mode'] == 'workflow':
filters.append(App.mode == 'completion') filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))
elif args['mode'] == 'chat': elif args['mode'] == 'chat':
filters.append(App.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: else:
pass pass
if 'name' in args and args['name']: if 'name' in args and args['name']:
filters.append(App.name.ilike(f'%{args["name"]}%')) name = args['name'][:30]
filters.append(App.name.ilike(f'%{name}%'))
app_models = db.paginate( app_models = db.paginate(
db.select(App).where(*filters).order_by(App.created_at.desc()), db.select(App).where(*filters).order_by(App.created_at.desc()),
...@@ -78,10 +90,9 @@ class AppListApi(Resource): ...@@ -78,10 +90,9 @@ class AppListApi(Resource):
"""Create app""" """Create app"""
parser = reqparse.RequestParser() parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, location='json') parser.add_argument('name', type=str, required=True, location='json')
parser.add_argument('mode', type=str, choices=['chat', 'agent', 'workflow'], location='json') parser.add_argument('mode', type=str, choices=ALLOW_CREATE_APP_MODES, location='json')
parser.add_argument('icon', type=str, location='json') parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json') parser.add_argument('icon_background', type=str, location='json')
parser.add_argument('model_config', type=dict, location='json')
args = parser.parse_args() args = parser.parse_args()
# The role of the current user in the ta table must be admin or owner # The role of the current user in the ta table must be admin or owner
...@@ -139,15 +150,15 @@ class AppListApi(Resource): ...@@ -139,15 +150,15 @@ class AppListApi(Resource):
app_mode = AppMode.value_of(args['mode']) app_mode = AppMode.value_of(args['mode'])
model_config_template = model_templates[app_mode.value + '_default'] app_template = default_app_templates[app_mode]
app = App(**model_config_template['app'])
app_model_config = AppModelConfig(**model_config_template['model_config'])
if app_mode in [AppMode.CHAT, AppMode.AGENT]: # get model config
default_model_config = app_template['model_config']
if 'model' in default_model_config:
# get model provider # get model provider
model_manager = ModelManager() model_manager = ModelManager()
# get default model instance
try: try:
model_instance = model_manager.get_default_model_instance( model_instance = model_manager.get_default_model_instance(
tenant_id=current_user.current_tenant_id, tenant_id=current_user.current_tenant_id,
...@@ -157,10 +168,25 @@ class AppListApi(Resource): ...@@ -157,10 +168,25 @@ class AppListApi(Resource):
model_instance = None model_instance = None
if model_instance: if model_instance:
model_dict = app_model_config.model_dict if model_instance.model == default_model_config['model']['name']:
model_dict['provider'] = model_instance.provider default_model_dict = default_model_config['model']
model_dict['name'] = model_instance.model else:
app_model_config.model = json.dumps(model_dict) 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_model_config = AppModelConfig(**default_model_config)
app.name = args['name'] app.name = args['name']
app.mode = args['mode'] app.mode = args['mode']
...@@ -193,24 +219,95 @@ class AppListApi(Resource): ...@@ -193,24 +219,95 @@ class AppListApi(Resource):
app_was_created.send(app) app_was_created.send(app)
return app, 201 return app, 201
class AppTemplateApi(Resource):
class AppImportApi(Resource):
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@marshal_with(template_list_fields) @marshal_with(app_detail_fields)
def get(self): @cloud_edition_billing_resource_check('apps')
"""Get app demo templates""" def post(self):
"""Import app"""
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
parser = reqparse.RequestParser()
parser.add_argument('data', type=str, required=True, nullable=False, location='json')
parser.add_argument('name', type=str, location='json')
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
args = parser.parse_args()
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(
enable_site=True,
enable_api=True,
is_demo=False,
api_rpm=0,
api_rph=0,
status='normal'
)
app.tenant_id = current_user.current_tenant_id
app.mode = app_data.get('mode')
app.name = args.get("name") if args.get("name") else app_data.get('name')
app.icon = args.get("icon") if args.get("icon") else app_data.get('icon')
app.icon_background = args.get("icon_background") if args.get("icon_background") \
else app_data.get('icon_background')
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
account = current_user account = current_user
interface_language = account.interface_language
templates = demo_model_templates.get(interface_language) site = Site(
if not templates: app_id=app.id,
templates = demo_model_templates.get(languages[0]) title=app.name,
default_language=account.interface_language,
customize_token_strategy='not_allow',
code=Site.generate_code(16)
)
db.session.add(site)
db.session.commit()
app_was_created.send(app)
return {'data': templates} return app, 201
class AppApi(Resource): class AppApi(Resource):
...@@ -244,6 +341,38 @@ class AppApi(Resource): ...@@ -244,6 +341,38 @@ class AppApi(Resource):
return {'result': 'success'}, 204 return {'result': 'success'}, 204
class AppExportApi(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
"""Export app"""
app_model_config = app_model.app_model_config
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 {
"data": yaml.dump(export_data)
}
class AppNameApi(Resource): class AppNameApi(Resource):
@setup_required @setup_required
@login_required @login_required
...@@ -321,57 +450,10 @@ class AppApiStatus(Resource): ...@@ -321,57 +450,10 @@ class AppApiStatus(Resource):
return app_model return app_model
class AppCopy(Resource):
@staticmethod
def create_app_copy(app):
copy_app = App(
name=app.name + ' copy',
icon=app.icon,
icon_background=app.icon_background,
tenant_id=app.tenant_id,
mode=app.mode,
app_model_config_id=app.app_model_config_id,
enable_site=app.enable_site,
enable_api=app.enable_api,
api_rpm=app.api_rpm,
api_rph=app.api_rph
)
return copy_app
@staticmethod
def create_app_model_config_copy(app_config, copy_app_id):
copy_app_model_config = app_config.copy()
copy_app_model_config.app_id = copy_app_id
return copy_app_model_config
@setup_required
@login_required
@account_initialization_required
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_model):
copy_app = self.create_app_copy(app_model)
db.session.add(copy_app)
app_config = db.session.query(AppModelConfig). \
filter(AppModelConfig.app_id == app_model.id). \
one_or_none()
if app_config:
copy_app_model_config = self.create_app_model_config_copy(app_config, copy_app.id)
db.session.add(copy_app_model_config)
db.session.commit()
copy_app.app_model_config_id = copy_app_model_config.id
db.session.commit()
return copy_app, 201
api.add_resource(AppListApi, '/apps') api.add_resource(AppListApi, '/apps')
api.add_resource(AppTemplateApi, '/app-templates') api.add_resource(AppImportApi, '/apps/import')
api.add_resource(AppApi, '/apps/<uuid:app_id>') api.add_resource(AppApi, '/apps/<uuid:app_id>')
api.add_resource(AppCopy, '/apps/<uuid:app_id>/copy') api.add_resource(AppExportApi, '/apps/<uuid:app_id>/export')
api.add_resource(AppNameApi, '/apps/<uuid:app_id>/name') api.add_resource(AppNameApi, '/apps/<uuid:app_id>/name')
api.add_resource(AppIconApi, '/apps/<uuid:app_id>/icon') api.add_resource(AppIconApi, '/apps/<uuid:app_id>/icon')
api.add_resource(AppSiteStatus, '/apps/<uuid:app_id>/site-enable') api.add_resource(AppSiteStatus, '/apps/<uuid:app_id>/site-enable')
......
...@@ -7,7 +7,7 @@ from controllers.console.setup import setup_required ...@@ -7,7 +7,7 @@ from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required from controllers.console.wraps import account_initialization_required
from fields.workflow_fields import workflow_fields from fields.workflow_fields import workflow_fields
from libs.login import current_user, login_required from libs.login import current_user, login_required
from models.model import App, AppMode, ChatbotAppEngine from models.model import App, AppMode
from services.workflow_service import WorkflowService from services.workflow_service import WorkflowService
...@@ -15,7 +15,7 @@ class DraftWorkflowApi(Resource): ...@@ -15,7 +15,7 @@ class DraftWorkflowApi(Resource):
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.WORKFLOW], app_engine=ChatbotAppEngine.WORKFLOW) @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@marshal_with(workflow_fields) @marshal_with(workflow_fields)
def get(self, app_model: App): def get(self, app_model: App):
""" """
...@@ -34,7 +34,7 @@ class DraftWorkflowApi(Resource): ...@@ -34,7 +34,7 @@ class DraftWorkflowApi(Resource):
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.WORKFLOW], app_engine=ChatbotAppEngine.WORKFLOW) @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def post(self, app_model: App): def post(self, app_model: App):
""" """
Sync draft workflow Sync draft workflow
...@@ -55,7 +55,7 @@ class DefaultBlockConfigApi(Resource): ...@@ -55,7 +55,7 @@ class DefaultBlockConfigApi(Resource):
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@get_app_model(mode=[AppMode.CHAT, AppMode.WORKFLOW], app_engine=ChatbotAppEngine.WORKFLOW) @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def get(self, app_model: App): def get(self, app_model: App):
""" """
Get default block config Get default block config
...@@ -72,7 +72,8 @@ class ConvertToWorkflowApi(Resource): ...@@ -72,7 +72,8 @@ class ConvertToWorkflowApi(Resource):
@get_app_model(mode=[AppMode.CHAT, AppMode.COMPLETION]) @get_app_model(mode=[AppMode.CHAT, AppMode.COMPLETION])
def post(self, app_model: App): def post(self, app_model: App):
""" """
Convert basic mode of chatbot app(expert mode) to workflow mode Convert basic mode of chatbot app to workflow mode
Convert expert mode of chatbot app to workflow mode
Convert Completion App to Workflow App Convert Completion App to Workflow App
""" """
# convert to workflow mode # convert to workflow mode
......
...@@ -5,12 +5,11 @@ from typing import Optional, Union ...@@ -5,12 +5,11 @@ from typing import Optional, Union
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 libs.login import current_user from libs.login import current_user
from models.model import App, AppMode, ChatbotAppEngine from models.model import App, AppMode
def get_app_model(view: Optional[Callable] = None, *, def get_app_model(view: Optional[Callable] = None, *,
mode: Union[AppMode, list[AppMode]] = None, mode: Union[AppMode, list[AppMode]] = None):
app_engine: ChatbotAppEngine = None):
def decorator(view_func): def decorator(view_func):
@wraps(view_func) @wraps(view_func)
def decorated_view(*args, **kwargs): def decorated_view(*args, **kwargs):
...@@ -32,6 +31,9 @@ def get_app_model(view: Optional[Callable] = None, *, ...@@ -32,6 +31,9 @@ def get_app_model(view: Optional[Callable] = None, *,
raise AppNotFoundError() raise AppNotFoundError()
app_mode = AppMode.value_of(app_model.mode) app_mode = AppMode.value_of(app_model.mode)
if app_mode == AppMode.CHANNEL:
raise AppNotFoundError()
if mode is not None: if mode is not None:
if isinstance(mode, list): if isinstance(mode, list):
modes = mode modes = mode
...@@ -42,16 +44,6 @@ def get_app_model(view: Optional[Callable] = None, *, ...@@ -42,16 +44,6 @@ def get_app_model(view: Optional[Callable] = None, *,
mode_values = {m.value for m in modes} mode_values = {m.value for m in modes}
raise AppNotFoundError(f"App mode is not in the supported list: {mode_values}") raise AppNotFoundError(f"App mode is not in the supported list: {mode_values}")
if app_engine is not None:
if app_mode not in [AppMode.CHAT, AppMode.WORKFLOW]:
raise AppNotFoundError(f"App mode is not supported for {app_engine.value} app engine.")
if app_mode == AppMode.CHAT:
# fetch current app model config
app_model_config = app_model.app_model_config
if not app_model_config or app_model_config.chatbot_app_engine != app_engine.value:
raise AppNotFoundError(f"{app_engine.value} app engine is not supported.")
kwargs['app_model'] = app_model kwargs['app_model'] = app_model
return view_func(*args, **kwargs) return view_func(*args, **kwargs)
......
...@@ -34,8 +34,7 @@ class InstalledAppsListApi(Resource): ...@@ -34,8 +34,7 @@ class InstalledAppsListApi(Resource):
'is_pinned': installed_app.is_pinned, 'is_pinned': installed_app.is_pinned,
'last_used_at': installed_app.last_used_at, 'last_used_at': installed_app.last_used_at,
'editable': current_user.role in ["owner", "admin"], 'editable': current_user.role in ["owner", "admin"],
'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id, 'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id
'is_agent': installed_app.is_agent
} }
for installed_app in installed_apps for installed_app in installed_apps
] ]
......
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
...@@ -6,6 +9,7 @@ from controllers.console import api ...@@ -6,6 +9,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
app_fields = { app_fields = {
'id': fields.String, 'id': fields.String,
...@@ -23,8 +27,7 @@ recommended_app_fields = { ...@@ -23,8 +27,7 @@ recommended_app_fields = {
'privacy_policy': fields.String, 'privacy_policy': fields.String,
'category': fields.String, 'category': fields.String,
'position': fields.Integer, 'position': fields.Integer,
'is_listed': fields.Boolean, 'is_listed': fields.Boolean
'is_agent': fields.Boolean
} }
recommended_app_list_fields = { recommended_app_list_fields = {
...@@ -73,8 +76,7 @@ class RecommendedAppListApi(Resource): ...@@ -73,8 +76,7 @@ class RecommendedAppListApi(Resource):
'privacy_policy': site.privacy_policy, 'privacy_policy': site.privacy_policy,
'category': recommended_app.category, 'category': recommended_app.category,
'position': recommended_app.position, 'position': recommended_app.position,
'is_listed': recommended_app.is_listed, 'is_listed': recommended_app.is_listed
"is_agent": app.is_agent
} }
recommended_apps_result.append(recommended_app_result) recommended_apps_result.append(recommended_app_result)
...@@ -84,27 +86,6 @@ class RecommendedAppListApi(Resource): ...@@ -84,27 +86,6 @@ class RecommendedAppListApi(Resource):
class RecommendedAppApi(Resource): class RecommendedAppApi(Resource):
model_config_fields = {
'opening_statement': fields.String,
'suggested_questions': fields.Raw(attribute='suggested_questions_list'),
'suggested_questions_after_answer': fields.Raw(attribute='suggested_questions_after_answer_dict'),
'more_like_this': fields.Raw(attribute='more_like_this_dict'),
'model': fields.Raw(attribute='model_dict'),
'user_input_form': fields.Raw(attribute='user_input_form_list'),
'pre_prompt': fields.String,
'agent_mode': fields.Raw(attribute='agent_mode_dict'),
}
app_simple_detail_fields = {
'id': fields.String,
'name': fields.String,
'icon': fields.String,
'icon_background': fields.String,
'mode': fields.String,
'app_model_config': fields.Nested(model_config_fields),
}
@marshal_with(app_simple_detail_fields)
def get(self, app_id): def get(self, app_id):
app_id = str(app_id) app_id = str(app_id)
...@@ -118,11 +99,38 @@ class RecommendedAppApi(Resource): ...@@ -118,11 +99,38 @@ class RecommendedAppApi(Resource):
raise AppNotFoundError raise AppNotFoundError
# get app detail # get app detail
app = db.session.query(App).filter(App.id == app_id).first() app_model = db.session.query(App).filter(App.id == app_id).first()
if not app or not app.is_public: if not app_model or not app_model.is_public:
raise AppNotFoundError raise AppNotFoundError
return app app_model_config = app_model.app_model_config
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 {
'id': app_model.id,
'name': app_model.name,
'icon': app_model.icon,
'icon_background': app_model.icon_background,
'mode': app_model.mode,
'export_data': yaml.dump(export_data)
}
api.add_resource(RecommendedAppListApi, '/explore/apps') api.add_resource(RecommendedAppListApi, '/explore/apps')
......
...@@ -235,7 +235,7 @@ class ProviderManager: ...@@ -235,7 +235,7 @@ class ProviderManager:
if available_models: if available_models:
found = False found = False
for available_model in available_models: for available_model in available_models:
if available_model.model == "gpt-3.5-turbo-1106": if available_model.model == "gpt-4":
default_model = TenantDefaultModel( default_model = TenantDefaultModel(
tenant_id=tenant_id, tenant_id=tenant_id,
model_type=model_type.to_origin_model_type(), model_type=model_type.to_origin_model_type(),
......
...@@ -42,14 +42,10 @@ app_detail_fields = { ...@@ -42,14 +42,10 @@ app_detail_fields = {
'id': fields.String, 'id': fields.String,
'name': fields.String, 'name': fields.String,
'mode': fields.String, 'mode': fields.String,
'is_agent': fields.Boolean,
'icon': fields.String, 'icon': fields.String,
'icon_background': fields.String, 'icon_background': fields.String,
'enable_site': fields.Boolean, 'enable_site': fields.Boolean,
'enable_api': fields.Boolean, 'enable_api': fields.Boolean,
'api_rpm': fields.Integer,
'api_rph': fields.Integer,
'is_demo': fields.Boolean,
'model_config': fields.Nested(model_config_fields, attribute='app_model_config'), 'model_config': fields.Nested(model_config_fields, attribute='app_model_config'),
'created_at': TimestampField 'created_at': TimestampField
} }
...@@ -67,12 +63,8 @@ app_partial_fields = { ...@@ -67,12 +63,8 @@ app_partial_fields = {
'id': fields.String, 'id': fields.String,
'name': fields.String, 'name': fields.String,
'mode': fields.String, 'mode': fields.String,
'is_agent': fields.Boolean,
'icon': fields.String, 'icon': fields.String,
'icon_background': fields.String, 'icon_background': fields.String,
'enable_site': fields.Boolean,
'enable_api': fields.Boolean,
'is_demo': fields.Boolean,
'model_config': fields.Nested(model_config_partial_fields, attribute='app_model_config'), 'model_config': fields.Nested(model_config_partial_fields, attribute='app_model_config'),
'created_at': TimestampField 'created_at': TimestampField
} }
...@@ -122,10 +114,6 @@ app_detail_fields_with_site = { ...@@ -122,10 +114,6 @@ app_detail_fields_with_site = {
'icon_background': fields.String, 'icon_background': fields.String,
'enable_site': fields.Boolean, 'enable_site': fields.Boolean,
'enable_api': fields.Boolean, 'enable_api': fields.Boolean,
'api_rpm': fields.Integer,
'api_rph': fields.Integer,
'is_agent': fields.Boolean,
'is_demo': fields.Boolean,
'model_config': fields.Nested(model_config_fields, attribute='app_model_config'), 'model_config': fields.Nested(model_config_fields, attribute='app_model_config'),
'site': fields.Nested(site_fields), 'site': fields.Nested(site_fields),
'api_base_url': fields.String, 'api_base_url': fields.String,
......
...@@ -17,8 +17,7 @@ installed_app_fields = { ...@@ -17,8 +17,7 @@ installed_app_fields = {
'is_pinned': fields.Boolean, 'is_pinned': fields.Boolean,
'last_used_at': TimestampField, 'last_used_at': TimestampField,
'editable': fields.Boolean, 'editable': fields.Boolean,
'uninstallable': fields.Boolean, 'uninstallable': fields.Boolean
'is_agent': fields.Boolean,
} }
installed_app_list_fields = { installed_app_list_fields = {
......
...@@ -107,7 +107,6 @@ def upgrade(): ...@@ -107,7 +107,6 @@ def upgrade():
batch_op.create_index('workflow_version_idx', ['tenant_id', 'app_id', 'version'], unique=False) batch_op.create_index('workflow_version_idx', ['tenant_id', 'app_id', 'version'], unique=False)
with op.batch_alter_table('app_model_configs', schema=None) as batch_op: with op.batch_alter_table('app_model_configs', schema=None) as batch_op:
batch_op.add_column(sa.Column('chatbot_app_engine', sa.String(length=255), server_default=sa.text("'normal'::character varying"), nullable=False))
batch_op.add_column(sa.Column('workflow_id', postgresql.UUID(), nullable=True)) batch_op.add_column(sa.Column('workflow_id', postgresql.UUID(), nullable=True))
with op.batch_alter_table('messages', schema=None) as batch_op: with op.batch_alter_table('messages', schema=None) as batch_op:
...@@ -123,7 +122,6 @@ def downgrade(): ...@@ -123,7 +122,6 @@ def downgrade():
with op.batch_alter_table('app_model_configs', schema=None) as batch_op: with op.batch_alter_table('app_model_configs', schema=None) as batch_op:
batch_op.drop_column('workflow_id') batch_op.drop_column('workflow_id')
batch_op.drop_column('chatbot_app_engine')
with op.batch_alter_table('workflows', schema=None) as batch_op: with op.batch_alter_table('workflows', schema=None) as batch_op:
batch_op.drop_index('workflow_version_idx') batch_op.drop_index('workflow_version_idx')
......
"""set model config column nullable
Revision ID: cc04d0998d4d
Revises: b289e2408ee2
Create Date: 2024-02-27 03:47:47.376325
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'cc04d0998d4d'
down_revision = 'b289e2408ee2'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('app_model_configs', schema=None) as batch_op:
batch_op.alter_column('provider',
existing_type=sa.VARCHAR(length=255),
nullable=True)
batch_op.alter_column('model_id',
existing_type=sa.VARCHAR(length=255),
nullable=True)
batch_op.alter_column('configs',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=True)
with op.batch_alter_table('apps', schema=None) as batch_op:
batch_op.alter_column('api_rpm',
existing_type=sa.Integer(),
server_default='0',
nullable=False)
batch_op.alter_column('api_rph',
existing_type=sa.Integer(),
server_default='0',
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('apps', schema=None) as batch_op:
batch_op.alter_column('api_rpm',
existing_type=sa.Integer(),
server_default=None,
nullable=False)
batch_op.alter_column('api_rph',
existing_type=sa.Integer(),
server_default=None,
nullable=False)
with op.batch_alter_table('app_model_configs', schema=None) as batch_op:
batch_op.alter_column('configs',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=False)
batch_op.alter_column('model_id',
existing_type=sa.VARCHAR(length=255),
nullable=False)
batch_op.alter_column('provider',
existing_type=sa.VARCHAR(length=255),
nullable=False)
# ### end Alembic commands ###
...@@ -31,7 +31,9 @@ class AppMode(Enum): ...@@ -31,7 +31,9 @@ class AppMode(Enum):
COMPLETION = 'completion' COMPLETION = 'completion'
WORKFLOW = 'workflow' WORKFLOW = 'workflow'
CHAT = 'chat' CHAT = 'chat'
AGENT = 'agent' ADVANCED_CHAT = 'advanced-chat'
AGENT_CHAT = 'agent-chat'
CHANNEL = 'channel'
@classmethod @classmethod
def value_of(cls, value: str) -> 'AppMode': def value_of(cls, value: str) -> 'AppMode':
...@@ -64,8 +66,8 @@ class App(db.Model): ...@@ -64,8 +66,8 @@ class App(db.Model):
status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying")) status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
enable_site = db.Column(db.Boolean, nullable=False) enable_site = db.Column(db.Boolean, nullable=False)
enable_api = db.Column(db.Boolean, nullable=False) enable_api = db.Column(db.Boolean, nullable=False)
api_rpm = db.Column(db.Integer, nullable=False) api_rpm = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
api_rph = db.Column(db.Integer, nullable=False) api_rph = db.Column(db.Integer, nullable=False, server_default=db.text('0'))
is_demo = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) is_demo = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
is_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) is_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
is_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) is_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
...@@ -92,19 +94,7 @@ class App(db.Model): ...@@ -92,19 +94,7 @@ class App(db.Model):
def tenant(self): def tenant(self):
tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first() tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
return tenant return tenant
@property
def is_agent(self) -> bool:
app_model_config = self.app_model_config
if not app_model_config:
return False
if not app_model_config.agent_mode:
return False
if self.app_model_config.agent_mode_dict.get('enabled', False) \
and self.app_model_config.agent_mode_dict.get('strategy', '') in ['function_call', 'react']:
return True
return False
@property @property
def deleted_tools(self) -> list: def deleted_tools(self) -> list:
# get agent mode tools # get agent mode tools
...@@ -153,11 +143,6 @@ class App(db.Model): ...@@ -153,11 +143,6 @@ class App(db.Model):
return deleted_tools return deleted_tools
class ChatbotAppEngine(Enum):
NORMAL = 'normal'
WORKFLOW = 'workflow'
class AppModelConfig(db.Model): class AppModelConfig(db.Model):
__tablename__ = 'app_model_configs' __tablename__ = 'app_model_configs'
__table_args__ = ( __table_args__ = (
...@@ -167,9 +152,9 @@ class AppModelConfig(db.Model): ...@@ -167,9 +152,9 @@ class AppModelConfig(db.Model):
id = db.Column(UUID, server_default=db.text('uuid_generate_v4()')) id = db.Column(UUID, server_default=db.text('uuid_generate_v4()'))
app_id = db.Column(UUID, nullable=False) app_id = db.Column(UUID, nullable=False)
provider = db.Column(db.String(255), nullable=False) provider = db.Column(db.String(255), nullable=True)
model_id = db.Column(db.String(255), nullable=False) model_id = db.Column(db.String(255), nullable=True)
configs = db.Column(db.JSON, nullable=False) configs = db.Column(db.JSON, nullable=True)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
opening_statement = db.Column(db.Text) opening_statement = db.Column(db.Text)
...@@ -191,7 +176,6 @@ class AppModelConfig(db.Model): ...@@ -191,7 +176,6 @@ class AppModelConfig(db.Model):
dataset_configs = db.Column(db.Text) dataset_configs = db.Column(db.Text)
external_data_tools = db.Column(db.Text) external_data_tools = db.Column(db.Text)
file_upload = db.Column(db.Text) file_upload = db.Column(db.Text)
chatbot_app_engine = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
workflow_id = db.Column(UUID) workflow_id = db.Column(UUID)
@property @property
...@@ -301,9 +285,6 @@ class AppModelConfig(db.Model): ...@@ -301,9 +285,6 @@ class AppModelConfig(db.Model):
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"provider": "",
"model_id": "",
"configs": {},
"opening_statement": self.opening_statement, "opening_statement": self.opening_statement,
"suggested_questions": self.suggested_questions_list, "suggested_questions": self.suggested_questions_list,
"suggested_questions_after_answer": self.suggested_questions_after_answer_dict, "suggested_questions_after_answer": self.suggested_questions_after_answer_dict,
...@@ -327,9 +308,6 @@ class AppModelConfig(db.Model): ...@@ -327,9 +308,6 @@ class AppModelConfig(db.Model):
} }
def from_model_config_dict(self, model_config: dict): def from_model_config_dict(self, model_config: dict):
self.provider = ""
self.model_id = ""
self.configs = {}
self.opening_statement = model_config['opening_statement'] self.opening_statement = model_config['opening_statement']
self.suggested_questions = json.dumps(model_config['suggested_questions']) self.suggested_questions = json.dumps(model_config['suggested_questions'])
self.suggested_questions_after_answer = json.dumps(model_config['suggested_questions_after_answer']) self.suggested_questions_after_answer = json.dumps(model_config['suggested_questions_after_answer'])
...@@ -358,15 +336,13 @@ class AppModelConfig(db.Model): ...@@ -358,15 +336,13 @@ class AppModelConfig(db.Model):
if model_config.get('dataset_configs') else None if model_config.get('dataset_configs') else None
self.file_upload = json.dumps(model_config.get('file_upload')) \ self.file_upload = json.dumps(model_config.get('file_upload')) \
if model_config.get('file_upload') else None if model_config.get('file_upload') else None
self.workflow_id = model_config.get('workflow_id')
return self return self
def copy(self): def copy(self):
new_app_model_config = AppModelConfig( new_app_model_config = AppModelConfig(
id=self.id, id=self.id,
app_id=self.app_id, app_id=self.app_id,
provider="",
model_id="",
configs={},
opening_statement=self.opening_statement, opening_statement=self.opening_statement,
suggested_questions=self.suggested_questions, suggested_questions=self.suggested_questions,
suggested_questions_after_answer=self.suggested_questions_after_answer, suggested_questions_after_answer=self.suggested_questions_after_answer,
...@@ -385,7 +361,8 @@ class AppModelConfig(db.Model): ...@@ -385,7 +361,8 @@ class AppModelConfig(db.Model):
chat_prompt_config=self.chat_prompt_config, chat_prompt_config=self.chat_prompt_config,
completion_prompt_config=self.completion_prompt_config, completion_prompt_config=self.completion_prompt_config,
dataset_configs=self.dataset_configs, dataset_configs=self.dataset_configs,
file_upload=self.file_upload file_upload=self.file_upload,
workflow_id=self.workflow_id
) )
return new_app_model_config return new_app_model_config
...@@ -446,12 +423,6 @@ class InstalledApp(db.Model): ...@@ -446,12 +423,6 @@ class InstalledApp(db.Model):
tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first() tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
return tenant return tenant
@property
def is_agent(self) -> bool:
app = self.app
if not app:
return False
return app.is_agent
class Conversation(db.Model): class Conversation(db.Model):
__tablename__ = 'conversations' __tablename__ = 'conversations'
......
...@@ -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, ChatbotAppEngine, Site from models.model import App, AppMode, AppModelConfig, Site
from models.workflow import Workflow, WorkflowType from models.workflow import Workflow, WorkflowType
...@@ -85,8 +85,6 @@ class WorkflowConverter: ...@@ -85,8 +85,6 @@ class WorkflowConverter:
new_app_model_config.chat_prompt_config = '' new_app_model_config.chat_prompt_config = ''
new_app_model_config.completion_prompt_config = '' new_app_model_config.completion_prompt_config = ''
new_app_model_config.dataset_configs = '' new_app_model_config.dataset_configs = ''
new_app_model_config.chatbot_app_engine = ChatbotAppEngine.WORKFLOW.value \
if app_model.mode == AppMode.CHAT.value else ChatbotAppEngine.NORMAL.value
new_app_model_config.workflow_id = workflow.id new_app_model_config.workflow_id = workflow.id
db.session.add(new_app_model_config) db.session.add(new_app_model_config)
......
import json import json
from datetime import datetime from datetime import datetime
from typing import Optional
from extensions.ext_database import db from extensions.ext_database import db
from models.account import Account from models.account import Account
from models.model import App, AppMode, ChatbotAppEngine from models.model import App, AppMode
from models.workflow import Workflow, WorkflowType from models.workflow import Workflow, WorkflowType
from services.workflow.defaults import default_block_configs from services.workflow.defaults import default_block_configs
from services.workflow.workflow_converter import WorkflowConverter from services.workflow.workflow_converter import WorkflowConverter
...@@ -58,6 +59,40 @@ class WorkflowService: ...@@ -58,6 +59,40 @@ class WorkflowService:
# return draft workflow # return draft workflow
return workflow return workflow
def publish_draft_workflow(self, app_model: App,
account: Account,
draft_workflow: Optional[Workflow] = None) -> Workflow:
"""
Publish draft workflow
:param app_model: App instance
:param account: Account instance
:param draft_workflow: Workflow instance
"""
if not draft_workflow:
# fetch draft workflow by app_model
draft_workflow = self.get_draft_workflow(app_model=app_model)
if not draft_workflow:
raise ValueError('No valid workflow found.')
# create new workflow
workflow = Workflow(
tenant_id=app_model.tenant_id,
app_id=app_model.id,
type=draft_workflow.type,
version=str(datetime.utcnow()),
graph=draft_workflow.graph,
created_by=account.id
)
# commit db session changes
db.session.add(workflow)
db.session.commit()
# return new workflow
return workflow
def get_default_block_configs(self) -> dict: def get_default_block_configs(self) -> dict:
""" """
Get default block configs Get default block configs
...@@ -77,11 +112,7 @@ class WorkflowService: ...@@ -77,11 +112,7 @@ class WorkflowService:
# chatbot convert to workflow mode # chatbot convert to workflow mode
workflow_converter = WorkflowConverter() workflow_converter = WorkflowConverter()
if app_model.mode == AppMode.CHAT.value: if app_model.mode not in [AppMode.CHAT.value, AppMode.COMPLETION.value]:
# check if chatbot app is in basic mode
if app_model.app_model_config.chatbot_app_engine != ChatbotAppEngine.NORMAL:
raise ValueError('Chatbot app already in workflow mode')
elif app_model.mode != AppMode.COMPLETION.value:
raise ValueError(f'Current App mode: {app_model.mode} is not supported convert to workflow.') raise ValueError(f'Current App mode: {app_model.mode} is not supported convert to workflow.')
# convert to workflow # convert to workflow
......
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