Skip to content

Commit

Permalink
Merge commit 'e88ea71aefd2adca64683ce0cca836df015d0f4f'
Browse files Browse the repository at this point in the history
* commit 'e88ea71aefd2adca64683ce0cca836df015d0f4f': (91 commits)
  chore/bump version to 0.14.2 (langgenius#12017)
  fix: issue Multiple Paths Between IF/ELSE Branches (langgenius#11646)
  Fix/refactor invoke result handling in question classifier node (langgenius#12015)
  fix: remove json_schema if response format is disabled. (langgenius#12014)
  fix: add UUID validation for tool file ID extraction (langgenius#12011)
  fix: add logging for missing edge mapping in StreamProcessor (langgenius#12008)
  Fix/add retry mechanism to billing service request handling (langgenius#12006)
  Fix/workflow retry log (langgenius#12013)
  fix: add retry feature to code node (langgenius#12005)
  fix: drop useless and wrong code in Account (langgenius#11961)
  fix: improve error handling for file retrieval in AwsS3Storage (langgenius#12002)
  Fix/workflow retry (langgenius#11999)
  feat: add RequestBodyError for invalid request body handling (langgenius#11994)
  fix: Introduce ArrayVariable and update iteration node to handle it (langgenius#12001)
  fix: remove unused credential validation logic in VectorizerProvider (langgenius#12000)
  feat: Warning on invite modal when mail setup is incomplete (langgenius#11809)
  fix: handle broader request exceptions in OAuth process (langgenius#11997)
  fix: Multiple Paths Between IF/ELSE Branches Invalidate Conditions  (langgenius#11544)
  fix: retry node in iteration logs wrong (langgenius#11995)
  fix: remove the unused retry index field (langgenius#11903)
  ...

# Conflicts:
#	web/yarn.lock
  • Loading branch information
Scorpion1221 committed Dec 24, 2024
2 parents f0a0b66 + e88ea71 commit d26836d
Show file tree
Hide file tree
Showing 294 changed files with 5,592 additions and 1,750 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/api-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
- name: Run ModelRuntime
run: poetry run -C api bash dev/pytest/pytest_model_runtime.sh

- name: Run dify config tests
run: poetry run -C api python dev/pytest/pytest_config_tests.py

- name: Run Tool
run: poetry run -C api bash dev/pytest/pytest_tools.sh

Expand Down
1 change: 1 addition & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
WORKFLOW_MAX_EXECUTION_STEPS=500
WORKFLOW_MAX_EXECUTION_TIME=1200
WORKFLOW_CALL_MAX_DEPTH=5
WORKFLOW_PARALLEL_DEPTH_LIMIT=3
MAX_VARIABLE_SIZE=204800

# App configuration
Expand Down
1 change: 0 additions & 1 deletion api/.ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ ignore = [
"SIM113", # eumerate-for-loop
"SIM117", # multiple-with-statements
"SIM210", # if-expr-with-true-false
"SIM300", # yoda-conditions,
]

[lint.per-file-ignores]
Expand Down
3 changes: 2 additions & 1 deletion api/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,8 @@ def create_tenant(email: str, language: Optional[str] = None, name: Optional[str
if language not in languages:
language = "en-US"

name = name.strip()
# Validates name encoding for non-Latin characters.
name = name.strip().encode("utf-8").decode("utf-8") if name else None

# generate random password
new_password = secrets.token_urlsafe(16)
Expand Down
5 changes: 5 additions & 0 deletions api/configs/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,11 @@ class WorkflowConfig(BaseSettings):
default=5,
)

WORKFLOW_PARALLEL_DEPTH_LIMIT: PositiveInt = Field(
description="Maximum allowed depth for nested parallel executions",
default=3,
)

MAX_VARIABLE_SIZE: PositiveInt = Field(
description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.",
default=200 * 1024,
Expand Down
2 changes: 1 addition & 1 deletion api/configs/packaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):

CURRENT_VERSION: str = Field(
description="Dify version",
default="0.14.1",
default="0.14.2",
)

COMMIT_SHA: str = Field(
Expand Down
5 changes: 5 additions & 0 deletions api/controllers/common/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
class FilenameNotExistsError(HTTPException):
code = 400
description = "The specified filename does not exist."


class RemoteFileUploadError(HTTPException):
code = 400
description = "Error uploading remote file."
2 changes: 1 addition & 1 deletion api/controllers/console/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def decorated(*args, **kwargs):
if auth_scheme != "bearer":
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")

if dify_config.ADMIN_API_KEY != auth_token:
if auth_token != dify_config.ADMIN_API_KEY:
raise Unauthorized("API key is invalid.")

return view(*args, **kwargs)
Expand Down
15 changes: 15 additions & 0 deletions api/controllers/console/app/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound

import services
from configs import dify_config
from controllers.console import api
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
from controllers.console.app.wraps import get_app_model
Expand Down Expand Up @@ -426,7 +427,21 @@ def post(self, app_model: App):
}


class WorkflowConfigApi(Resource):
"""Resource for workflow configuration."""

@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def get(self, app_model: App):
return {
"parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT,
}


api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
api.add_resource(WorkflowConfigApi, "/apps/<uuid:app_id>/workflows/draft/config")
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
api.add_resource(DraftWorkflowRunApi, "/apps/<uuid:app_id>/workflows/draft/run")
api.add_resource(WorkflowTaskStopApi, "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop")
Expand Down
3 changes: 1 addition & 2 deletions api/controllers/console/app/wraps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from controllers.console.app.error import AppNotFoundError
from extensions.ext_database import db
from libs.login import current_user
from models import App
from models.model import AppMode
from models import App, AppMode


def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode]] = None):
Expand Down
2 changes: 1 addition & 1 deletion api/controllers/console/auth/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def get(self, provider: str):
try:
token = oauth_provider.get_access_token(code)
user_info = oauth_provider.get_user_info(token)
except requests.exceptions.HTTPError as e:
except requests.exceptions.RequestException as e:
logging.exception(f"An error occurred during the OAuth process with {provider}: {e.response.text}")
return {"error": "OAuth process failed"}, 400

Expand Down
20 changes: 12 additions & 8 deletions api/controllers/console/explore/conversation.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from flask_login import current_user
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound

from controllers.console import api
from controllers.console.explore.error import NotChatAppError
from controllers.console.explore.wraps import InstalledAppResource
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
Expand Down Expand Up @@ -34,14 +36,16 @@ def get(self, installed_app):
pinned = True if args["pinned"] == "true" else False

try:
return WebConversationService.pagination_by_last_id(
app_model=app_model,
user=current_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.EXPLORE,
pinned=pinned,
)
with Session(db.engine) as session:
return WebConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=current_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.EXPLORE,
pinned=pinned,
)
except LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

Expand Down
2 changes: 1 addition & 1 deletion api/controllers/console/explore/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def post(self, installed_app, message_id):
args = parser.parse_args()

try:
MessageService.create_feedback(app_model, message_id, current_user, args["rating"])
MessageService.create_feedback(app_model, message_id, current_user, args["rating"], args["content"])
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")

Expand Down
1 change: 1 addition & 0 deletions api/controllers/console/explore/recommended_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"name": fields.String,
"mode": fields.String,
"icon": fields.String,
"icon_type": fields.String,
"icon_url": AppIconUrlField,
"icon_background": fields.String,
}
Expand Down
13 changes: 9 additions & 4 deletions api/controllers/console/remote_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import services
from controllers.common import helpers
from controllers.common.errors import RemoteFileUploadError
from core.file import helpers as file_helpers
from core.helper import ssrf_proxy
from fields.file_fields import file_fields_with_signed_url, remote_file_info_fields
Expand Down Expand Up @@ -43,10 +44,14 @@ def post(self):

url = args["url"]

resp = ssrf_proxy.head(url=url)
if resp.status_code != httpx.codes.OK:
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
resp.raise_for_status()
try:
resp = ssrf_proxy.head(url=url)
if resp.status_code != httpx.codes.OK:
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
if resp.status_code != httpx.codes.OK:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
except httpx.RequestError as e:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")

file_info = helpers.guess_file_info_from_response(resp)

Expand Down
24 changes: 14 additions & 10 deletions api/controllers/console/workspace/tool_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from flask import send_file
from flask_login import current_user
from flask_restful import Resource, reqparse
from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden

from configs import dify_config
from controllers.console import api
from controllers.console.wraps import account_initialization_required, enterprise_license_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db
from libs.helper import alphanumeric, uuid_value
from libs.login import login_required
from services.tools.api_tools_manage_service import ApiToolManageService
Expand Down Expand Up @@ -91,26 +93,28 @@ def post(self, provider):

args = parser.parse_args()

return BuiltinToolManageService.update_builtin_tool_provider(
user_id,
tenant_id,
provider,
args["credentials"],
)
with Session(db.engine) as session:
result = BuiltinToolManageService.update_builtin_tool_provider(
session=session,
user_id=user_id,
tenant_id=tenant_id,
provider_name=provider,
credentials=args["credentials"],
)
session.commit()
return result


class ToolBuiltinProviderGetCredentialsApi(Resource):
@setup_required
@login_required
@account_initialization_required
def get(self, provider):
user_id = current_user.id
tenant_id = current_user.current_tenant_id

return BuiltinToolManageService.get_builtin_tool_provider_credentials(
user_id,
tenant_id,
provider,
tenant_id=tenant_id,
provider_name=provider,
)


Expand Down
20 changes: 12 additions & 8 deletions api/controllers/service_api/app/conversation.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound

import services
from controllers.service_api import api
from controllers.service_api.app.error import NotChatAppError
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import (
conversation_delete_fields,
conversation_infinite_scroll_pagination_fields,
Expand Down Expand Up @@ -39,14 +41,16 @@ def get(self, app_model: App, end_user: EndUser):
args = parser.parse_args()

try:
return ConversationService.pagination_by_last_id(
app_model=app_model,
user=end_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.SERVICE_API,
sort_by=args["sort_by"],
)
with Session(db.engine) as session:
return ConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=end_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.SERVICE_API,
sort_by=args["sort_by"],
)
except services.errors.conversation.LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

Expand Down
3 changes: 2 additions & 1 deletion api/controllers/service_api/app/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ def post(self, app_model: App, end_user: EndUser, message_id):

parser = reqparse.RequestParser()
parser.add_argument("rating", type=str, choices=["like", "dislike", None], location="json")
parser.add_argument("content", type=str, location="json")
args = parser.parse_args()

try:
MessageService.create_feedback(app_model, message_id, end_user, args["rating"])
MessageService.create_feedback(app_model, message_id, end_user, args["rating"], args["content"])
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")

Expand Down
22 changes: 13 additions & 9 deletions api/controllers/web/conversation.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range
from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound

from controllers.web import api
from controllers.web.error import NotChatAppError
from controllers.web.wraps import WebApiResource
from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
from libs.helper import uuid_value
from models.model import AppMode
Expand Down Expand Up @@ -40,15 +42,17 @@ def get(self, app_model, end_user):
pinned = True if args["pinned"] == "true" else False

try:
return WebConversationService.pagination_by_last_id(
app_model=app_model,
user=end_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.WEB_APP,
pinned=pinned,
sort_by=args["sort_by"],
)
with Session(db.engine) as session:
return WebConversationService.pagination_by_last_id(
session=session,
app_model=app_model,
user=end_user,
last_id=args["last_id"],
limit=args["limit"],
invoke_from=InvokeFrom.WEB_APP,
pinned=pinned,
sort_by=args["sort_by"],
)
except LastConversationNotExistsError:
raise NotFound("Last Conversation Not Exists.")

Expand Down
2 changes: 1 addition & 1 deletion api/controllers/web/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def post(self, app_model, end_user, message_id):
args = parser.parse_args()

try:
MessageService.create_feedback(app_model, message_id, end_user, args["rating"])
MessageService.create_feedback(app_model, message_id, end_user, args["rating"], args["content"])
except services.errors.message.MessageNotExistsError:
raise NotFound("Message Not Exists.")

Expand Down
13 changes: 9 additions & 4 deletions api/controllers/web/remote_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import services
from controllers.common import helpers
from controllers.common.errors import RemoteFileUploadError
from controllers.web.wraps import WebApiResource
from core.file import helpers as file_helpers
from core.helper import ssrf_proxy
Expand Down Expand Up @@ -38,10 +39,14 @@ def post(self, app_model, end_user): # Add app_model and end_user parameters

url = args["url"]

resp = ssrf_proxy.head(url=url)
if resp.status_code != httpx.codes.OK:
resp = ssrf_proxy.get(url=url, timeout=3)
resp.raise_for_status()
try:
resp = ssrf_proxy.head(url=url)
if resp.status_code != httpx.codes.OK:
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
if resp.status_code != httpx.codes.OK:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
except httpx.RequestError as e:
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")

file_info = helpers.guess_file_info_from_response(resp)

Expand Down
Loading

0 comments on commit d26836d

Please sign in to comment.