mirror of
https://github.com/langgenius/dify.git
synced 2026-03-13 02:57:41 +08:00
205 lines
7.2 KiB
Python
205 lines
7.2 KiB
Python
import logging
|
|
|
|
import click
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from configs import dify_config
|
|
from events.app_event import app_was_created
|
|
from extensions.ext_database import db
|
|
from extensions.ext_redis import redis_client
|
|
from libs.db_migration_lock import DbMigrationAutoRenewLock
|
|
from libs.rsa import generate_key_pair
|
|
from models import Tenant
|
|
from models.model import App, AppMode, Conversation
|
|
from models.provider import Provider, ProviderModel
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DB_UPGRADE_LOCK_TTL_SECONDS = 60
|
|
|
|
|
|
@click.command(
|
|
"reset-encrypt-key-pair",
|
|
help="Reset the asymmetric key pair of workspace for encrypt LLM credentials. "
|
|
"After the reset, all LLM credentials will become invalid, "
|
|
"requiring re-entry."
|
|
"Only support SELF_HOSTED mode.",
|
|
)
|
|
@click.confirmation_option(
|
|
prompt=click.style(
|
|
"Are you sure you want to reset encrypt key pair? This operation cannot be rolled back!", fg="red"
|
|
)
|
|
)
|
|
def reset_encrypt_key_pair():
|
|
"""
|
|
Reset the encrypted key pair of workspace for encrypt LLM credentials.
|
|
After the reset, all LLM credentials will become invalid, requiring re-entry.
|
|
Only support SELF_HOSTED mode.
|
|
"""
|
|
if dify_config.EDITION != "SELF_HOSTED":
|
|
click.echo(click.style("This command is only for SELF_HOSTED installations.", fg="red"))
|
|
return
|
|
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
|
|
tenants = session.query(Tenant).all()
|
|
for tenant in tenants:
|
|
if not tenant:
|
|
click.echo(click.style("No workspaces found. Run /install first.", fg="red"))
|
|
return
|
|
|
|
tenant.encrypt_public_key = generate_key_pair(tenant.id)
|
|
|
|
session.query(Provider).where(Provider.provider_type == "custom", Provider.tenant_id == tenant.id).delete()
|
|
session.query(ProviderModel).where(ProviderModel.tenant_id == tenant.id).delete()
|
|
|
|
click.echo(
|
|
click.style(
|
|
f"Congratulations! The asymmetric key pair of workspace {tenant.id} has been reset.",
|
|
fg="green",
|
|
)
|
|
)
|
|
|
|
|
|
@click.command("convert-to-agent-apps", help="Convert Agent Assistant to Agent App.")
|
|
def convert_to_agent_apps():
|
|
"""
|
|
Convert Agent Assistant to Agent App.
|
|
"""
|
|
click.echo(click.style("Starting convert to agent apps.", fg="green"))
|
|
|
|
proceeded_app_ids = []
|
|
|
|
while True:
|
|
# fetch first 1000 apps
|
|
sql_query = """SELECT a.id AS id FROM apps a
|
|
INNER JOIN app_model_configs am ON a.app_model_config_id=am.id
|
|
WHERE a.mode = 'chat'
|
|
AND am.agent_mode is not null
|
|
AND (
|
|
am.agent_mode like '%"strategy": "function_call"%'
|
|
OR am.agent_mode like '%"strategy": "react"%'
|
|
)
|
|
AND (
|
|
am.agent_mode like '{"enabled": true%'
|
|
OR am.agent_mode like '{"max_iteration": %'
|
|
) ORDER BY a.created_at DESC LIMIT 1000
|
|
"""
|
|
|
|
with db.engine.begin() as conn:
|
|
rs = conn.execute(sa.text(sql_query))
|
|
|
|
apps = []
|
|
for i in rs:
|
|
app_id = str(i.id)
|
|
if app_id not in proceeded_app_ids:
|
|
proceeded_app_ids.append(app_id)
|
|
app = db.session.query(App).where(App.id == app_id).first()
|
|
if app is not None:
|
|
apps.append(app)
|
|
|
|
if len(apps) == 0:
|
|
break
|
|
|
|
for app in apps:
|
|
click.echo(f"Converting app: {app.id}")
|
|
|
|
try:
|
|
app.mode = AppMode.AGENT_CHAT
|
|
db.session.commit()
|
|
|
|
# update conversation mode to agent
|
|
db.session.query(Conversation).where(Conversation.app_id == app.id).update(
|
|
{Conversation.mode: AppMode.AGENT_CHAT}
|
|
)
|
|
|
|
db.session.commit()
|
|
click.echo(click.style(f"Converted app: {app.id}", fg="green"))
|
|
except Exception as e:
|
|
click.echo(click.style(f"Convert app error: {e.__class__.__name__} {str(e)}", fg="red"))
|
|
|
|
click.echo(click.style(f"Conversion complete. Converted {len(proceeded_app_ids)} agent apps.", fg="green"))
|
|
|
|
|
|
@click.command("upgrade-db", help="Upgrade the database")
|
|
def upgrade_db():
|
|
click.echo("Preparing database migration...")
|
|
lock = DbMigrationAutoRenewLock(
|
|
redis_client=redis_client,
|
|
name="db_upgrade_lock",
|
|
ttl_seconds=DB_UPGRADE_LOCK_TTL_SECONDS,
|
|
logger=logger,
|
|
log_context="db_migration",
|
|
)
|
|
if lock.acquire(blocking=False):
|
|
migration_succeeded = False
|
|
try:
|
|
click.echo(click.style("Starting database migration.", fg="green"))
|
|
|
|
# run db migration
|
|
import flask_migrate
|
|
|
|
flask_migrate.upgrade()
|
|
|
|
migration_succeeded = True
|
|
click.echo(click.style("Database migration successful!", fg="green"))
|
|
|
|
except Exception as e:
|
|
logger.exception("Failed to execute database migration")
|
|
click.echo(click.style(f"Database migration failed: {e}", fg="red"))
|
|
raise SystemExit(1)
|
|
finally:
|
|
status = "successful" if migration_succeeded else "failed"
|
|
lock.release_safely(status=status)
|
|
else:
|
|
click.echo("Database migration skipped")
|
|
|
|
|
|
@click.command("fix-app-site-missing", help="Fix app related site missing issue.")
|
|
def fix_app_site_missing():
|
|
"""
|
|
Fix app related site missing issue.
|
|
"""
|
|
click.echo(click.style("Starting fix for missing app-related sites.", fg="green"))
|
|
|
|
failed_app_ids = []
|
|
while True:
|
|
sql = """select apps.id as id from apps left join sites on sites.app_id=apps.id
|
|
where sites.id is null limit 1000"""
|
|
with db.engine.begin() as conn:
|
|
rs = conn.execute(sa.text(sql))
|
|
|
|
processed_count = 0
|
|
for i in rs:
|
|
processed_count += 1
|
|
app_id = str(i.id)
|
|
|
|
if app_id in failed_app_ids:
|
|
continue
|
|
|
|
try:
|
|
app = db.session.query(App).where(App.id == app_id).first()
|
|
if not app:
|
|
logger.info("App %s not found", app_id)
|
|
continue
|
|
|
|
tenant = app.tenant
|
|
if tenant:
|
|
accounts = tenant.get_accounts()
|
|
if not accounts:
|
|
logger.info("Fix failed for app %s", app.id)
|
|
continue
|
|
|
|
account = accounts[0]
|
|
logger.info("Fixing missing site for app %s", app.id)
|
|
app_was_created.send(app, account=account)
|
|
except Exception:
|
|
failed_app_ids.append(app_id)
|
|
click.echo(click.style(f"Failed to fix missing site for app {app_id}", fg="red"))
|
|
logger.exception("Failed to fix app related site missing issue, app_id: %s", app_id)
|
|
continue
|
|
|
|
if not processed_count:
|
|
break
|
|
|
|
click.echo(click.style("Fix for missing app-related sites completed successfully!", fg="green"))
|