Skip to content

Commit

Permalink
Merge branch 'langgenius:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
XiaoBa-Yu authored Dec 31, 2024
2 parents fe7db47 + 63a0b8b commit 753b589
Show file tree
Hide file tree
Showing 41 changed files with 908 additions and 460 deletions.
26 changes: 17 additions & 9 deletions api/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
from libs import version_utils

# preparation before creating app
version_utils.check_supported_python_version()
import os
import sys


def is_db_command():
import sys

if len(sys.argv) > 1 and sys.argv[0].endswith("flask") and sys.argv[1] == "db":
return True
return False
Expand All @@ -18,10 +14,22 @@ def is_db_command():

app = create_migrations_app()
else:
from app_factory import create_app
from libs import threadings_utils
if os.environ.get("FLASK_DEBUG", "False") != "True":
from gevent import monkey # type: ignore

# gevent
monkey.patch_all()

from grpc.experimental import gevent as grpc_gevent # type: ignore

threadings_utils.apply_gevent_threading_patch()
# grpc gevent
grpc_gevent.init_gevent()

import psycogreen.gevent # type: ignore

psycogreen.gevent.patch_psycopg()

from app_factory import create_app

app = create_app()
celery = app.extensions["celery"]
Expand Down
8 changes: 8 additions & 0 deletions api/configs/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,13 @@ class LoginConfig(BaseSettings):
)


class AccountConfig(BaseSettings):
ACCOUNT_DELETION_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
description="Duration in minutes for which a account deletion token remains valid",
default=5,
)


class FeatureConfig(
# place the configs in alphabet order
AppExecutionConfig,
Expand Down Expand Up @@ -792,6 +799,7 @@ class FeatureConfig(
WorkflowNodeExecutionConfig,
WorkspaceConfig,
LoginConfig,
AccountConfig,
# hosted services config
HostedServiceConfig,
CeleryBeatConfig,
Expand Down
6 changes: 6 additions & 0 deletions api/controllers/console/auth/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ class EmailCodeLoginRateLimitExceededError(BaseHTTPException):
error_code = "email_code_login_rate_limit_exceeded"
description = "Too many login emails have been sent. Please try again in 5 minutes."
code = 429


class EmailCodeAccountDeletionRateLimitExceededError(BaseHTTPException):
error_code = "email_code_account_deletion_rate_limit_exceeded"
description = "Too many account deletion emails have been sent. Please try again in 5 minutes."
code = 429
12 changes: 5 additions & 7 deletions api/controllers/console/auth/forgot_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@

from constants.languages import languages
from controllers.console import api
from controllers.console.auth.error import (
EmailCodeError,
InvalidEmailError,
InvalidTokenError,
PasswordMismatchError,
)
from controllers.console.error import AccountNotFound, EmailSendIpLimitError
from controllers.console.auth.error import EmailCodeError, InvalidEmailError, InvalidTokenError, PasswordMismatchError
from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
from controllers.console.wraps import setup_required
from events.tenant_event import tenant_was_created
from extensions.ext_database import db
from libs.helper import email, extract_remote_ip
from libs.password import hash_password, valid_password
from models.account import Account
from services.account_service import AccountService, TenantService
from services.errors.account import AccountRegisterError
from services.errors.workspace import WorkSpaceNotAllowedCreateError
from services.feature_service import FeatureService

Expand Down Expand Up @@ -129,6 +125,8 @@ def post(self):
)
except WorkSpaceNotAllowedCreateError:
pass
except AccountRegisterError as are:
raise AccountInFreezeError()

return {"result": "success"}

Expand Down
25 changes: 21 additions & 4 deletions api/controllers/console/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from flask_restful import Resource, reqparse # type: ignore

import services
from configs import dify_config
from constants.languages import languages
from controllers.console import api
from controllers.console.auth.error import (
Expand All @@ -16,6 +17,7 @@
)
from controllers.console.error import (
AccountBannedError,
AccountInFreezeError,
AccountNotFound,
EmailSendIpLimitError,
NotAllowedCreateWorkspace,
Expand All @@ -26,6 +28,8 @@
from libs.password import valid_password
from models.account import Account
from services.account_service import AccountService, RegisterService, TenantService
from services.billing_service import BillingService
from services.errors.account import AccountRegisterError
from services.errors.workspace import WorkSpaceNotAllowedCreateError
from services.feature_service import FeatureService

Expand All @@ -44,6 +48,9 @@ def post(self):
parser.add_argument("language", type=str, required=False, default="en-US", location="json")
args = parser.parse_args()

if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(args["email"]):
raise AccountInFreezeError()

is_login_error_rate_limit = AccountService.is_login_error_rate_limit(args["email"])
if is_login_error_rate_limit:
raise EmailPasswordLoginLimitError()
Expand Down Expand Up @@ -113,8 +120,10 @@ def post(self):
language = "zh-Hans"
else:
language = "en-US"

account = AccountService.get_user_through_email(args["email"])
try:
account = AccountService.get_user_through_email(args["email"])
except AccountRegisterError as are:
raise AccountInFreezeError()
if account is None:
if FeatureService.get_system_features().is_allow_register:
token = AccountService.send_reset_password_email(email=args["email"], language=language)
Expand Down Expand Up @@ -142,8 +151,11 @@ def post(self):
language = "zh-Hans"
else:
language = "en-US"
try:
account = AccountService.get_user_through_email(args["email"])
except AccountRegisterError as are:
raise AccountInFreezeError()

account = AccountService.get_user_through_email(args["email"])
if account is None:
if FeatureService.get_system_features().is_allow_register:
token = AccountService.send_email_code_login_email(email=args["email"], language=language)
Expand Down Expand Up @@ -177,7 +189,10 @@ def post(self):
raise EmailCodeError()

AccountService.revoke_email_code_login_token(args["token"])
account = AccountService.get_user_through_email(user_email)
try:
account = AccountService.get_user_through_email(user_email)
except AccountRegisterError as are:
raise AccountInFreezeError()
if account:
tenant = TenantService.get_join_tenants(account)
if not tenant:
Expand All @@ -196,6 +211,8 @@ def post(self):
)
except WorkSpaceNotAllowedCreateError:
return NotAllowedCreateWorkspace()
except AccountRegisterError as are:
raise AccountInFreezeError()
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))
AccountService.reset_login_error_rate_limit(args["email"])
return {"result": "success", "data": token_pair.model_dump()}
Expand Down
4 changes: 3 additions & 1 deletion api/controllers/console/auth/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from models import Account
from models.account import AccountStatus
from services.account_service import AccountService, RegisterService, TenantService
from services.errors.account import AccountNotFoundError
from services.errors.account import AccountNotFoundError, AccountRegisterError
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkSpaceNotFoundError
from services.feature_service import FeatureService

Expand Down Expand Up @@ -99,6 +99,8 @@ def get(self, provider: str):
f"{dify_config.CONSOLE_WEB_URL}/signin"
"?message=Workspace not found, please contact system admin to invite you to join in a workspace."
)
except AccountRegisterError as e:
return redirect(f"{dify_config.CONSOLE_WEB_URL}/signin?message={e.description}")

# Check account status
if account.status == AccountStatus.BANNED.value:
Expand Down
9 changes: 9 additions & 0 deletions api/controllers/console/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,12 @@ class UnauthorizedAndForceLogout(BaseHTTPException):
error_code = "unauthorized_and_force_logout"
description = "Unauthorized and force logout."
code = 401


class AccountInFreezeError(BaseHTTPException):
error_code = "account_in_freeze"
code = 400
description = (
"This email account has been deleted within the past 30 days"
"and is temporarily unavailable for new account registration."
)
53 changes: 53 additions & 0 deletions api/controllers/console/workspace/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from controllers.console.workspace.error import (
AccountAlreadyInitedError,
CurrentPasswordIncorrectError,
InvalidAccountDeletionCodeError,
InvalidInvitationCodeError,
RepeatPasswordNotMatchError,
)
Expand All @@ -21,6 +22,7 @@
from libs.login import login_required
from models import AccountIntegrate, InvitationCode
from services.account_service import AccountService
from services.billing_service import BillingService
from services.errors.account import CurrentPasswordIncorrectError as ServiceCurrentPasswordIncorrectError


Expand Down Expand Up @@ -242,6 +244,54 @@ def get(self):
return {"data": integrate_data}


class AccountDeleteVerifyApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self):
account = current_user

token, code = AccountService.generate_account_deletion_verification_code(account)
AccountService.send_account_deletion_verification_email(account, code)

return {"result": "success", "data": token}


class AccountDeleteApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self):
account = current_user

parser = reqparse.RequestParser()
parser.add_argument("token", type=str, required=True, location="json")
parser.add_argument("code", type=str, required=True, location="json")
args = parser.parse_args()

if not AccountService.verify_account_deletion_code(args["token"], args["code"]):
raise InvalidAccountDeletionCodeError()

AccountService.delete_account(account)

return {"result": "success"}


class AccountDeleteUpdateFeedbackApi(Resource):
@setup_required
def post(self):
account = current_user

parser = reqparse.RequestParser()
parser.add_argument("email", type=str, required=True, location="json")
parser.add_argument("feedback", type=str, required=True, location="json")
args = parser.parse_args()

BillingService.update_account_deletion_feedback(args["email"], args["feedback"])

return {"result": "success"}


# Register API resources
api.add_resource(AccountInitApi, "/account/init")
api.add_resource(AccountProfileApi, "/account/profile")
Expand All @@ -252,5 +302,8 @@ def get(self):
api.add_resource(AccountTimezoneApi, "/account/timezone")
api.add_resource(AccountPasswordApi, "/account/password")
api.add_resource(AccountIntegrateApi, "/account/integrates")
api.add_resource(AccountDeleteVerifyApi, "/account/delete/verify")
api.add_resource(AccountDeleteApi, "/account/delete")
api.add_resource(AccountDeleteUpdateFeedbackApi, "/account/delete/feedback")
# api.add_resource(AccountEmailApi, '/account/email')
# api.add_resource(AccountEmailVerifyApi, '/account/email-verify')
6 changes: 6 additions & 0 deletions api/controllers/console/workspace/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ class AccountNotInitializedError(BaseHTTPException):
error_code = "account_not_initialized"
description = "The account has not been initialized yet. Please proceed with the initialization process first."
code = 400


class InvalidAccountDeletionCodeError(BaseHTTPException):
error_code = "invalid_account_deletion_code"
description = "Invalid account deletion code."
code = 400
6 changes: 3 additions & 3 deletions api/core/app/task_pipeline/workflow_cycle_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def _handle_node_execution_start(
self, *, session: Session, workflow_run: WorkflowRun, event: QueueNodeStartedEvent
) -> WorkflowNodeExecution:
workflow_node_execution = WorkflowNodeExecution()
workflow_node_execution.id = event.node_execution_id
workflow_node_execution.id = str(uuid4())
workflow_node_execution.tenant_id = workflow_run.tenant_id
workflow_node_execution.app_id = workflow_run.app_id
workflow_node_execution.workflow_id = workflow_run.workflow_id
Expand Down Expand Up @@ -391,7 +391,7 @@ def _handle_workflow_node_execution_retried(
execution_metadata = json.dumps(merged_metadata)

workflow_node_execution = WorkflowNodeExecution()
workflow_node_execution.id = event.node_execution_id
workflow_node_execution.id = str(uuid4())
workflow_node_execution.tenant_id = workflow_run.tenant_id
workflow_node_execution.app_id = workflow_run.app_id
workflow_node_execution.workflow_id = workflow_run.workflow_id
Expand Down Expand Up @@ -824,7 +824,7 @@ def _get_workflow_run(self, *, session: Session, workflow_run_id: str) -> Workfl
return workflow_run

def _get_workflow_node_execution(self, session: Session, node_execution_id: str) -> WorkflowNodeExecution:
stmt = select(WorkflowNodeExecution).where(WorkflowNodeExecution.id == node_execution_id)
stmt = select(WorkflowNodeExecution).where(WorkflowNodeExecution.node_execution_id == node_execution_id)
workflow_node_execution = session.scalar(stmt)
if not workflow_node_execution:
raise WorkflowNodeExecutionNotFoundError(node_execution_id)
Expand Down
4 changes: 4 additions & 0 deletions api/core/tools/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ class ToolApiSchemaError(ValueError):

class ToolEngineInvokeError(Exception):
meta: ToolInvokeMeta

def __init__(self, meta, **kwargs):
self.meta = meta
super().__init__(**kwargs)
8 changes: 4 additions & 4 deletions api/core/tools/provider/builtin/aws/tools/bedrock_retrieve.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def _bedrock_retrieve(

retrieval_configuration = {"vectorSearchConfiguration": {"numberOfResults": num_results}}

# 如果有元数据过滤条件,则添加到检索配置中
# Add metadata filter to retrieval configuration if present
if metadata_filter:
retrieval_configuration["vectorSearchConfiguration"]["filter"] = metadata_filter

Expand Down Expand Up @@ -77,7 +77,7 @@ def _invoke(
if not query:
return self.create_text_message("Please input query")

# 获取元数据过滤条件(如果存在)
# Get metadata filter conditions (if they exist)
metadata_filter_str = tool_parameters.get("metadata_filter")
metadata_filter = json.loads(metadata_filter_str) if metadata_filter_str else None

Expand All @@ -86,7 +86,7 @@ def _invoke(
query_input=query,
knowledge_base_id=self.knowledge_base_id,
num_results=self.topk,
metadata_filter=metadata_filter, # 将元数据过滤条件传递给检索方法
metadata_filter=metadata_filter,
)

line = 5
Expand All @@ -109,7 +109,7 @@ def validate_parameters(self, parameters: dict[str, Any]) -> None:
if not parameters.get("query"):
raise ValueError("query is required")

# 可选:可以验证元数据过滤条件是否为有效的 JSON 字符串(如果提供)
# Optional: Validate if metadata filter is a valid JSON string (if provided)
metadata_filter_str = parameters.get("metadata_filter")
if metadata_filter_str and not isinstance(json.loads(metadata_filter_str), dict):
raise ValueError("metadata_filter must be a valid JSON object")
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ parameters:
llm_description: AWS region where the Bedrock Knowledge Base is located
form: form

- name: metadata_filter
type: string
required: false
- name: metadata_filter # Additional parameter for metadata filtering
type: string # String type, expects JSON-formatted filter conditions
required: false # Optional field - can be omitted
label:
en_US: Metadata Filter
zh_Hans: 元数据过滤器
Expand Down
Loading

0 comments on commit 753b589

Please sign in to comment.