Commit 6e3cd62e authored by takatost's avatar takatost

refactor app mode

add app import and export
parent 61b4bedc
This diff is collapsed.
import json
from models.model import AppMode
model_templates = {
default_app_templates = {
# workflow default mode
'workflow_default': {
AppMode.WORKFLOW: {
'app': {
'mode': 'workflow',
'mode': AppMode.WORKFLOW.value,
'enable_site': True,
'enable_api': True,
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
'enable_api': True
},
'model_config': {
'provider': '',
'model_id': '',
'configs': {}
}
'model_config': {}
},
# chat default mode
'chat_default': {
AppMode.CHAT: {
'app': {
'mode': 'chat',
'mode': AppMode.CHAT.value,
'enable_site': True,
'enable_api': True,
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
'enable_api': True
},
'model_config': {
'provider': 'openai',
'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({
'model': {
"provider": "openai",
"name": "gpt-4",
"mode": "chat",
......@@ -55,36 +30,42 @@ model_templates = {
"presence_penalty": 0,
"frequency_penalty": 0
}
})
}
}
},
# agent default mode
'agent_default': {
# advanced-chat default mode
AppMode.ADVANCED_CHAT: {
'app': {
'mode': 'agent',
'mode': AppMode.ADVANCED_CHAT.value,
'enable_site': True,
'enable_api': True,
'is_demo': False,
'api_rpm': 0,
'api_rph': 0,
'status': 'normal'
'enable_api': True
},
'model_config': {
'provider': 'openai',
'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': {
"provider": "openai",
"name": "gpt-4",
"mode": "chat",
"completion_params": {
"max_tokens": 512,
"temperature": 1,
"top_p": 1,
"presence_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",
"name": "gpt-4",
"mode": "chat",
......@@ -95,7 +76,7 @@ model_templates = {
"presence_penalty": 0,
"frequency_penalty": 0
}
})
}
}
},
}
......
This diff is collapsed.
......@@ -7,7 +7,7 @@ from controllers.console.setup import setup_required
from controllers.console.wraps import account_initialization_required
from fields.workflow_fields import workflow_fields
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
......@@ -15,7 +15,7 @@ class DraftWorkflowApi(Resource):
@setup_required
@login_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)
def get(self, app_model: App):
"""
......@@ -34,7 +34,7 @@ class DraftWorkflowApi(Resource):
@setup_required
@login_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):
"""
Sync draft workflow
......@@ -55,7 +55,7 @@ class DefaultBlockConfigApi(Resource):
@setup_required
@login_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):
"""
Get default block config
......@@ -72,7 +72,8 @@ class ConvertToWorkflowApi(Resource):
@get_app_model(mode=[AppMode.CHAT, AppMode.COMPLETION])
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 to workflow mode
......
......@@ -5,12 +5,11 @@ from typing import Optional, Union
from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db
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, *,
mode: Union[AppMode, list[AppMode]] = None,
app_engine: ChatbotAppEngine = None):
mode: Union[AppMode, list[AppMode]] = None):
def decorator(view_func):
@wraps(view_func)
def decorated_view(*args, **kwargs):
......@@ -32,6 +31,9 @@ def get_app_model(view: Optional[Callable] = None, *,
raise AppNotFoundError()
app_mode = AppMode.value_of(app_model.mode)
if app_mode == AppMode.CHANNEL:
raise AppNotFoundError()
if mode is not None:
if isinstance(mode, list):
modes = mode
......@@ -42,16 +44,6 @@ def get_app_model(view: Optional[Callable] = None, *,
mode_values = {m.value for m in modes}
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
return view_func(*args, **kwargs)
......
......@@ -34,8 +34,7 @@ class InstalledAppsListApi(Resource):
'is_pinned': installed_app.is_pinned,
'last_used_at': installed_app.last_used_at,
'editable': current_user.role in ["owner", "admin"],
'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id,
'is_agent': installed_app.is_agent
'uninstallable': current_tenant_id == installed_app.app_owner_tenant_id
}
for installed_app in installed_apps
]
......
import json
import yaml
from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse
......@@ -6,6 +9,7 @@ from controllers.console import api
from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db
from models.model import App, RecommendedApp
from services.workflow_service import WorkflowService
app_fields = {
'id': fields.String,
......@@ -23,8 +27,7 @@ recommended_app_fields = {
'privacy_policy': fields.String,
'category': fields.String,
'position': fields.Integer,
'is_listed': fields.Boolean,
'is_agent': fields.Boolean
'is_listed': fields.Boolean
}
recommended_app_list_fields = {
......@@ -73,8 +76,7 @@ class RecommendedAppListApi(Resource):
'privacy_policy': site.privacy_policy,
'category': recommended_app.category,
'position': recommended_app.position,
'is_listed': recommended_app.is_listed,
"is_agent": app.is_agent
'is_listed': recommended_app.is_listed
}
recommended_apps_result.append(recommended_app_result)
......@@ -84,27 +86,6 @@ class RecommendedAppListApi(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):
app_id = str(app_id)
......@@ -118,11 +99,38 @@ class RecommendedAppApi(Resource):
raise AppNotFoundError
# get app detail
app = db.session.query(App).filter(App.id == app_id).first()
if not app or not app.is_public:
app_model = db.session.query(App).filter(App.id == app_id).first()
if not app_model or not app_model.is_public:
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')
......
......@@ -235,7 +235,7 @@ class ProviderManager:
if available_models:
found = False
for available_model in available_models:
if available_model.model == "gpt-3.5-turbo-1106":
if available_model.model == "gpt-4":
default_model = TenantDefaultModel(
tenant_id=tenant_id,
model_type=model_type.to_origin_model_type(),
......
......@@ -42,14 +42,10 @@ app_detail_fields = {
'id': fields.String,
'name': fields.String,
'mode': fields.String,
'is_agent': fields.Boolean,
'icon': fields.String,
'icon_background': fields.String,
'enable_site': 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'),
'created_at': TimestampField
}
......@@ -67,12 +63,8 @@ app_partial_fields = {
'id': fields.String,
'name': fields.String,
'mode': fields.String,
'is_agent': fields.Boolean,
'icon': 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'),
'created_at': TimestampField
}
......@@ -122,10 +114,6 @@ app_detail_fields_with_site = {
'icon_background': fields.String,
'enable_site': 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'),
'site': fields.Nested(site_fields),
'api_base_url': fields.String,
......
......@@ -17,8 +17,7 @@ installed_app_fields = {
'is_pinned': fields.Boolean,
'last_used_at': TimestampField,
'editable': fields.Boolean,
'uninstallable': fields.Boolean,
'is_agent': fields.Boolean,
'uninstallable': fields.Boolean
}
installed_app_list_fields = {
......
......@@ -107,7 +107,6 @@ def upgrade():
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:
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))
with op.batch_alter_table('messages', schema=None) as batch_op:
......@@ -123,7 +122,6 @@ def downgrade():
with op.batch_alter_table('app_model_configs', schema=None) as batch_op:
batch_op.drop_column('workflow_id')
batch_op.drop_column('chatbot_app_engine')
with op.batch_alter_table('workflows', schema=None) as batch_op:
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):
COMPLETION = 'completion'
WORKFLOW = 'workflow'
CHAT = 'chat'
AGENT = 'agent'
ADVANCED_CHAT = 'advanced-chat'
AGENT_CHAT = 'agent-chat'
CHANNEL = 'channel'
@classmethod
def value_of(cls, value: str) -> 'AppMode':
......@@ -64,8 +66,8 @@ class App(db.Model):
status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
enable_site = db.Column(db.Boolean, nullable=False)
enable_api = db.Column(db.Boolean, nullable=False)
api_rpm = db.Column(db.Integer, nullable=False)
api_rph = 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, server_default=db.text('0'))
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_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
......@@ -92,19 +94,7 @@ class App(db.Model):
def tenant(self):
tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
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
def deleted_tools(self) -> list:
# get agent mode tools
......@@ -153,11 +143,6 @@ class App(db.Model):
return deleted_tools
class ChatbotAppEngine(Enum):
NORMAL = 'normal'
WORKFLOW = 'workflow'
class AppModelConfig(db.Model):
__tablename__ = 'app_model_configs'
__table_args__ = (
......@@ -167,9 +152,9 @@ class AppModelConfig(db.Model):
id = db.Column(UUID, server_default=db.text('uuid_generate_v4()'))
app_id = db.Column(UUID, nullable=False)
provider = db.Column(db.String(255), nullable=False)
model_id = db.Column(db.String(255), nullable=False)
configs = db.Column(db.JSON, nullable=False)
provider = db.Column(db.String(255), nullable=True)
model_id = db.Column(db.String(255), nullable=True)
configs = db.Column(db.JSON, nullable=True)
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)'))
opening_statement = db.Column(db.Text)
......@@ -191,7 +176,6 @@ class AppModelConfig(db.Model):
dataset_configs = db.Column(db.Text)
external_data_tools = 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)
@property
......@@ -301,9 +285,6 @@ class AppModelConfig(db.Model):
def to_dict(self) -> dict:
return {
"provider": "",
"model_id": "",
"configs": {},
"opening_statement": self.opening_statement,
"suggested_questions": self.suggested_questions_list,
"suggested_questions_after_answer": self.suggested_questions_after_answer_dict,
......@@ -327,9 +308,6 @@ class AppModelConfig(db.Model):
}
def from_model_config_dict(self, model_config: dict):
self.provider = ""
self.model_id = ""
self.configs = {}
self.opening_statement = model_config['opening_statement']
self.suggested_questions = json.dumps(model_config['suggested_questions'])
self.suggested_questions_after_answer = json.dumps(model_config['suggested_questions_after_answer'])
......@@ -358,15 +336,13 @@ class AppModelConfig(db.Model):
if model_config.get('dataset_configs') else None
self.file_upload = json.dumps(model_config.get('file_upload')) \
if model_config.get('file_upload') else None
self.workflow_id = model_config.get('workflow_id')
return self
def copy(self):
new_app_model_config = AppModelConfig(
id=self.id,
app_id=self.app_id,
provider="",
model_id="",
configs={},
opening_statement=self.opening_statement,
suggested_questions=self.suggested_questions,
suggested_questions_after_answer=self.suggested_questions_after_answer,
......@@ -385,7 +361,8 @@ class AppModelConfig(db.Model):
chat_prompt_config=self.chat_prompt_config,
completion_prompt_config=self.completion_prompt_config,
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
......@@ -446,12 +423,6 @@ class InstalledApp(db.Model):
tenant = db.session.query(Tenant).filter(Tenant.id == self.tenant_id).first()
return tenant
@property
def is_agent(self) -> bool:
app = self.app
if not app:
return False
return app.is_agent
class Conversation(db.Model):
__tablename__ = 'conversations'
......
......@@ -21,7 +21,7 @@ from events.app_event import app_was_created
from extensions.ext_database import db
from models.account import Account
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
......@@ -85,8 +85,6 @@ class WorkflowConverter:
new_app_model_config.chat_prompt_config = ''
new_app_model_config.completion_prompt_config = ''
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
db.session.add(new_app_model_config)
......
import json
from datetime import datetime
from typing import Optional
from extensions.ext_database import db
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 services.workflow.defaults import default_block_configs
from services.workflow.workflow_converter import WorkflowConverter
......@@ -58,6 +59,40 @@ class WorkflowService:
# return draft 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:
"""
Get default block configs
......@@ -77,11 +112,7 @@ class WorkflowService:
# chatbot convert to workflow mode
workflow_converter = WorkflowConverter()
if app_model.mode == AppMode.CHAT.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:
if app_model.mode not in [AppMode.CHAT.value, AppMode.COMPLETION.value]:
raise ValueError(f'Current App mode: {app_model.mode} is not supported 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