From 68d357d7f68441d3067b901aa491c0d8f2ed95b7 Mon Sep 17 00:00:00 2001 From: Boris Polonsky Date: Wed, 5 Nov 2025 17:19:08 +0800 Subject: [PATCH 1/5] Add WEAVIATE_GRPC_ENDPOINT as designed in weaviate migration guide (#27861) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/configs/middleware/vdb/weaviate_config.py | 5 +++++ .../vdb/weaviate/weaviate_vector.py | 22 ++++++++++++++++--- docker/.env.example | 1 + docker/docker-compose.yaml | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/api/configs/middleware/vdb/weaviate_config.py b/api/configs/middleware/vdb/weaviate_config.py index 6a79412ab8..aa81c870f6 100644 --- a/api/configs/middleware/vdb/weaviate_config.py +++ b/api/configs/middleware/vdb/weaviate_config.py @@ -22,6 +22,11 @@ class WeaviateConfig(BaseSettings): default=True, ) + WEAVIATE_GRPC_ENDPOINT: str | None = Field( + description="URL of the Weaviate gRPC server (e.g., 'grpc://localhost:50051' or 'grpcs://weaviate.example.com:443')", + default=None, + ) + WEAVIATE_BATCH_SIZE: PositiveInt = Field( description="Number of objects to be processed in a single batch operation (default is 100)", default=100, diff --git a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py index dceade0af9..39eb91e50e 100644 --- a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py +++ b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py @@ -39,11 +39,13 @@ class WeaviateConfig(BaseModel): Attributes: endpoint: Weaviate server endpoint URL + grpc_endpoint: Optional Weaviate gRPC server endpoint URL api_key: Optional API key for authentication batch_size: Number of objects to batch per insert operation """ endpoint: str + grpc_endpoint: str | None = None api_key: str | None = None batch_size: int = 100 @@ -88,9 +90,22 @@ class WeaviateVector(BaseVector): http_secure = p.scheme == "https" http_port = p.port or (443 if http_secure else 80) - grpc_host = host - grpc_secure = http_secure - grpc_port = 443 if grpc_secure else 50051 + # Parse gRPC configuration + if config.grpc_endpoint: + # Urls without scheme won't be parsed correctly in some python verions, + # see https://bugs.python.org/issue27657 + grpc_endpoint_with_scheme = ( + config.grpc_endpoint if "://" in config.grpc_endpoint else f"grpc://{config.grpc_endpoint}" + ) + grpc_p = urlparse(grpc_endpoint_with_scheme) + grpc_host = grpc_p.hostname or "localhost" + grpc_port = grpc_p.port or (443 if grpc_p.scheme == "grpcs" else 50051) + grpc_secure = grpc_p.scheme == "grpcs" + else: + # Infer from HTTP endpoint as fallback + grpc_host = host + grpc_secure = http_secure + grpc_port = 443 if grpc_secure else 50051 client = weaviate.connect_to_custom( http_host=host, @@ -432,6 +447,7 @@ class WeaviateVectorFactory(AbstractVectorFactory): collection_name=collection_name, config=WeaviateConfig( endpoint=dify_config.WEAVIATE_ENDPOINT or "", + grpc_endpoint=dify_config.WEAVIATE_GRPC_ENDPOINT or "", api_key=dify_config.WEAVIATE_API_KEY, batch_size=dify_config.WEAVIATE_BATCH_SIZE, ), diff --git a/docker/.env.example b/docker/.env.example index 7c7a938fd9..c19084ebbf 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -492,6 +492,7 @@ VECTOR_INDEX_NAME_PREFIX=Vector_index # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. WEAVIATE_ENDPOINT=http://weaviate:8080 WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_GRPC_ENDPOINT=grpc://weaviate:50051 # The Qdrant endpoint URL. Only available when VECTOR_STORE is `qdrant`. QDRANT_URL=http://qdrant:6333 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index fda53f8d93..1ff33a94b5 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -157,6 +157,7 @@ x-shared-env: &shared-api-worker-env VECTOR_INDEX_NAME_PREFIX: ${VECTOR_INDEX_NAME_PREFIX:-Vector_index} WEAVIATE_ENDPOINT: ${WEAVIATE_ENDPOINT:-http://weaviate:8080} WEAVIATE_API_KEY: ${WEAVIATE_API_KEY:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} + WEAVIATE_GRPC_ENDPOINT: ${WEAVIATE_GRPC_ENDPOINT:-grpc://weaviate:50051} QDRANT_URL: ${QDRANT_URL:-http://qdrant:6333} QDRANT_API_KEY: ${QDRANT_API_KEY:-difyai123456} QDRANT_CLIENT_TIMEOUT: ${QDRANT_CLIENT_TIMEOUT:-20} From 97a2e2ec2e30b385138ab84f3809941b8024d9b2 Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Wed, 5 Nov 2025 17:20:40 +0800 Subject: [PATCH 2/5] Fix: correct DraftWorkflowApi.post response model (#27289) Signed-off-by: Yongtao Huang Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/console/app/workflow.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 56771ed420..5f41b65e88 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -102,7 +102,18 @@ class DraftWorkflowApi(Resource): }, ) ) - @api.response(200, "Draft workflow synced successfully", workflow_fields) + @api.response( + 200, + "Draft workflow synced successfully", + api.model( + "SyncDraftWorkflowResponse", + { + "result": fields.String, + "hash": fields.String, + "updated_at": fields.String, + }, + ), + ) @api.response(400, "Invalid workflow configuration") @api.response(403, "Permission denied") @edit_permission_required From 87fb9a6b6985bd6827137b28e6cf412ede6103f8 Mon Sep 17 00:00:00 2001 From: Cursx <33718736+Cursx@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:37:19 +0800 Subject: [PATCH 3/5] fix Version 2.0.0-beta.2: Chat annotations Api Error #25506 (#27206) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Asuka Minato --- api/controllers/service_api/wraps.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index fe1e2c419b..319b7bd780 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -67,6 +67,7 @@ def validate_app_token(view: Callable[P, R] | None = None, *, fetch_user_arg: Fe kwargs["app_model"] = app_model + # If caller needs end-user context, attach EndUser to current_user if fetch_user_arg: if fetch_user_arg.fetch_from == WhereisUserArg.QUERY: user_id = request.args.get("user") @@ -75,7 +76,6 @@ def validate_app_token(view: Callable[P, R] | None = None, *, fetch_user_arg: Fe elif fetch_user_arg.fetch_from == WhereisUserArg.FORM: user_id = request.form.get("user") else: - # use default-user user_id = None if not user_id and fetch_user_arg.required: @@ -90,6 +90,28 @@ def validate_app_token(view: Callable[P, R] | None = None, *, fetch_user_arg: Fe # Set EndUser as current logged-in user for flask_login.current_user current_app.login_manager._update_request_context_with_user(end_user) # type: ignore user_logged_in.send(current_app._get_current_object(), user=end_user) # type: ignore + else: + # For service API without end-user context, ensure an Account is logged in + # so services relying on current_account_with_tenant() work correctly. + tenant_owner_info = ( + db.session.query(Tenant, Account) + .join(TenantAccountJoin, Tenant.id == TenantAccountJoin.tenant_id) + .join(Account, TenantAccountJoin.account_id == Account.id) + .where( + Tenant.id == app_model.tenant_id, + TenantAccountJoin.role == "owner", + Tenant.status == TenantStatus.NORMAL, + ) + .one_or_none() + ) + + if tenant_owner_info: + tenant_model, account = tenant_owner_info + account.current_tenant = tenant_model + current_app.login_manager._update_request_context_with_user(account) # type: ignore + user_logged_in.send(current_app._get_current_object(), user=current_user) # type: ignore + else: + raise Unauthorized("Tenant owner account not found or tenant is not active.") return view_func(*args, **kwargs) From f627348b11a90f96a2f0ff1a4e9e92da4a68d43a Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:42:07 +0800 Subject: [PATCH 4/5] fix jina reader creadential migration command (#27883) --- api/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/commands.py b/api/commands.py index 084fd576a1..8698ec3f97 100644 --- a/api/commands.py +++ b/api/commands.py @@ -1601,7 +1601,7 @@ def transform_datasource_credentials(): "integration_secret": api_key, } datasource_provider = DatasourceProvider( - provider="jina", + provider="jinareader", tenant_id=tenant_id, plugin_id=jina_plugin_id, auth_type=api_key_credential_type.value, From 61a0fcc2eaaf7033700b0268a29d1782f6cd6e6d Mon Sep 17 00:00:00 2001 From: red_sun <56100962+redSun64@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:41:05 +0800 Subject: [PATCH 5/5] fix agent putout the output of workflow-tool twice (#26835) (#27087) --- api/core/tools/__base/tool.py | 5 +++-- api/core/tools/entities/tool_entities.py | 1 + api/core/tools/tool_engine.py | 3 +++ api/core/tools/workflow_as_tool/tool.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/api/core/tools/__base/tool.py b/api/core/tools/__base/tool.py index 82616596f8..8ca4eabb7a 100644 --- a/api/core/tools/__base/tool.py +++ b/api/core/tools/__base/tool.py @@ -210,12 +210,13 @@ class Tool(ABC): meta=meta, ) - def create_json_message(self, object: dict) -> ToolInvokeMessage: + def create_json_message(self, object: dict, suppress_output: bool = False) -> ToolInvokeMessage: """ create a json message """ return ToolInvokeMessage( - type=ToolInvokeMessage.MessageType.JSON, message=ToolInvokeMessage.JsonMessage(json_object=object) + type=ToolInvokeMessage.MessageType.JSON, + message=ToolInvokeMessage.JsonMessage(json_object=object, suppress_output=suppress_output), ) def create_variable_message( diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index 15a4f0aafd..5b385f1bb2 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -129,6 +129,7 @@ class ToolInvokeMessage(BaseModel): class JsonMessage(BaseModel): json_object: dict + suppress_output: bool = Field(default=False, description="Whether to suppress JSON output in result string") class BlobMessage(BaseModel): blob: bytes diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index 92d441b5ac..13fd579e20 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -245,6 +245,9 @@ class ToolEngine: + "you do not need to create it, just tell the user to check it now." ) elif response.type == ToolInvokeMessage.MessageType.JSON: + json_message = cast(ToolInvokeMessage.JsonMessage, response.message) + if json_message.suppress_output: + continue json_parts.append( json.dumps( safe_json_value(cast(ToolInvokeMessage.JsonMessage, response.message).json_object), diff --git a/api/core/tools/workflow_as_tool/tool.py b/api/core/tools/workflow_as_tool/tool.py index 2cd46647a0..5703c19c88 100644 --- a/api/core/tools/workflow_as_tool/tool.py +++ b/api/core/tools/workflow_as_tool/tool.py @@ -117,7 +117,7 @@ class WorkflowTool(Tool): self._latest_usage = self._derive_usage_from_result(data) yield self.create_text_message(json.dumps(outputs, ensure_ascii=False)) - yield self.create_json_message(outputs) + yield self.create_json_message(outputs, suppress_output=True) @property def latest_usage(self) -> LLMUsage: