mirror of
https://github.com/langgenius/dify.git
synced 2026-09-09 05:41:00 +08:00
fix(api): align service api schemas with runtime behavior (#41943)
This commit is contained in:
parent
146b193ef7
commit
7ba2cfb30b
@ -239,12 +239,12 @@ to the legacy area and avoid importing RESTX model objects from controllers.
|
||||
|
||||
## Verifying Swagger
|
||||
|
||||
For schema and documentation changes, run focused tests and generate Swagger JSON:
|
||||
For schema and documentation changes, run focused tests and generate Swagger JSON from the repository root:
|
||||
|
||||
```bash
|
||||
uv run --project . pytest tests/unit_tests/controllers/common/test_schema.py
|
||||
uv run --project . pytest tests/unit_tests/commands/test_generate_swagger_specs.py tests/unit_tests/controllers/test_swagger.py
|
||||
uv run --project . dev/generate_swagger_specs.py --output-dir /tmp/dify-openapi-check
|
||||
uv run --project api pytest api/tests/unit_tests/controllers/common/test_schema.py
|
||||
uv run --project api pytest api/tests/unit_tests/commands/test_generate_swagger_specs.py api/tests/unit_tests/controllers/test_swagger.py
|
||||
uv run --project api python api/dev/generate_swagger_specs.py --output-dir /tmp/dify-openapi-check
|
||||
```
|
||||
|
||||
Inspect affected endpoints with `jq`. Check that:
|
||||
@ -253,3 +253,17 @@ Inspect affected endpoints with `jq`. Check that:
|
||||
- Request bodies appear only where the endpoint has a body.
|
||||
- Responses reference the expected `*Response` schema.
|
||||
- Response schemas use public serialized names, not internal validation aliases like `inputs_dict`.
|
||||
|
||||
## Service API Documentation Handoff
|
||||
|
||||
The `dify-docs` repository imports `service-openapi.json` for the `/v1` API. It keeps a pinned export and adds
|
||||
descriptions, examples, translations, and existing page URLs through documentation annotations. Fields, constraints,
|
||||
references, response statuses, and security come from this repository's export.
|
||||
|
||||
Verify the public schema against request validation and response serialization, including intentional schema overrides
|
||||
and excluded fields. Fix an inaccurate contract here and regenerate it before updating the documentation snapshot.
|
||||
Record the full source commit SHA when handing off the export. For coordinated changes that are not committed yet,
|
||||
also provide the source patch used to produce it; replace that patch with a clean committed export after merging.
|
||||
|
||||
The manual import, annotation review, build, and validation commands live in `tools/api-pipeline/README.md` in
|
||||
`dify-docs`. Regenerate this repository's OpenAPI Markdown and TypeScript/Zod contracts whenever their inputs change.
|
||||
|
||||
@ -150,7 +150,6 @@ class AnnotationReplyActionStatusApi(Resource):
|
||||
responses={
|
||||
200: "Job status retrieved successfully",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Job not found",
|
||||
}
|
||||
)
|
||||
@service_api_ns.response(
|
||||
|
||||
@ -60,7 +60,6 @@ class AppParameterApi(Resource):
|
||||
responses={
|
||||
200: "Parameters retrieved successfully",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Application not found",
|
||||
}
|
||||
)
|
||||
@service_api_ns.response(200, "Parameters retrieved successfully", service_api_ns.models[Parameters.__name__])
|
||||
@ -88,6 +87,7 @@ class AppMetaApi(Resource):
|
||||
tags=["Applications"],
|
||||
responses={
|
||||
200: "Successfully retrieved application meta information.",
|
||||
400: "`app_unavailable` : App unavailable or misconfigured.",
|
||||
},
|
||||
)
|
||||
@service_api_ns.doc("get_app_meta")
|
||||
@ -96,7 +96,6 @@ class AppMetaApi(Resource):
|
||||
responses={
|
||||
200: "Metadata retrieved successfully",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Application not found",
|
||||
}
|
||||
)
|
||||
@service_api_ns.response(200, "Metadata retrieved successfully", service_api_ns.models[AppMetaResponse.__name__])
|
||||
@ -122,6 +121,7 @@ class AppInfoApi(Resource):
|
||||
tags=["Applications"],
|
||||
responses={
|
||||
200: "Basic information of the application.",
|
||||
400: "`app_unavailable` : App unavailable or misconfigured.",
|
||||
},
|
||||
)
|
||||
@service_api_ns.doc("get_app_info")
|
||||
@ -130,7 +130,6 @@ class AppInfoApi(Resource):
|
||||
responses={
|
||||
200: "Application info retrieved successfully",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Application not found",
|
||||
}
|
||||
)
|
||||
@service_api_ns.response(
|
||||
|
||||
@ -208,7 +208,6 @@ class CompletionApi(Resource):
|
||||
200: "Completion created successfully",
|
||||
400: "Bad request - invalid parameters",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Conversation not found",
|
||||
500: "Internal server error",
|
||||
}
|
||||
)
|
||||
|
||||
@ -315,7 +315,8 @@ class WorkflowRunApi(Resource):
|
||||
),
|
||||
429: (
|
||||
"- `too_many_requests` : Too many concurrent requests for this app.\n"
|
||||
"- `rate_limit_error` : The upstream model provider rate limit was exceeded."
|
||||
"- `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution "
|
||||
"quota was exceeded."
|
||||
),
|
||||
500: "`internal_server_error` : Internal server error.",
|
||||
},
|
||||
@ -329,7 +330,6 @@ class WorkflowRunApi(Resource):
|
||||
200: "Workflow executed successfully",
|
||||
400: "Bad request - invalid parameters or workflow issues",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Workflow not found",
|
||||
429: "Rate limit exceeded",
|
||||
500: "Internal server error",
|
||||
}
|
||||
@ -430,7 +430,8 @@ class WorkflowRunByIdApi(Resource):
|
||||
404: "`not_found` : Workflow not found.",
|
||||
429: (
|
||||
"- `too_many_requests` : Too many concurrent requests for this app.\n"
|
||||
"- `rate_limit_error` : The upstream model provider rate limit was exceeded."
|
||||
"- `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution "
|
||||
"quota was exceeded."
|
||||
),
|
||||
500: "`internal_server_error` : Internal server error.",
|
||||
},
|
||||
|
||||
@ -35,7 +35,8 @@ from controllers.service_api.wraps import (
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_provider_manager
|
||||
from core.rag.index_processor.constant.index_type import IndexTechniqueType
|
||||
from fields.base import ResponseModel
|
||||
from fields.dataset_fields import DatasetDetailResponse, dataset_detail_response_source
|
||||
from fields.dataset_fields import DatasetDetailResponse as BaseDatasetDetailResponse
|
||||
from fields.dataset_fields import dataset_detail_response_source
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
from libs.helper import dump_response
|
||||
from libs.login import current_user
|
||||
@ -88,6 +89,11 @@ PartialMemberList = Annotated[
|
||||
]
|
||||
|
||||
|
||||
class DatasetDetailResponse(BaseDatasetDetailResponse):
|
||||
# The Service API dump helpers exclude Console permission metadata.
|
||||
permission_keys: list[str] = Field(default_factory=list, exclude=True)
|
||||
|
||||
|
||||
_SERVICE_DATASET_DETAIL_EXCLUDE = {"permission_keys"}
|
||||
_SERVICE_DATASET_LIST_EXCLUDE = {"data": {"__all__": _SERVICE_DATASET_DETAIL_EXCLUDE}}
|
||||
|
||||
@ -738,18 +744,11 @@ class DatasetApi(DatasetApiResource):
|
||||
|
||||
@service_api_ns.doc(
|
||||
summary="Delete Knowledge Base",
|
||||
description=(
|
||||
"Permanently delete a knowledge base and all its documents. The knowledge base must not be "
|
||||
"in use by any application."
|
||||
),
|
||||
description="Permanently delete a knowledge base and all its documents.",
|
||||
tags=["Knowledge Bases"],
|
||||
responses={
|
||||
204: "Success.",
|
||||
404: "`not_found` : Dataset not found.",
|
||||
409: (
|
||||
"`dataset_in_use` : The knowledge base is being used by some apps. Please remove it from the "
|
||||
"apps before deleting."
|
||||
),
|
||||
},
|
||||
)
|
||||
@service_api_ns.doc("delete_dataset")
|
||||
@ -760,7 +759,6 @@ class DatasetApi(DatasetApiResource):
|
||||
204: "Dataset deleted successfully",
|
||||
401: "Unauthorized - invalid API token",
|
||||
404: "Dataset not found",
|
||||
409: "Conflict - dataset is in use",
|
||||
}
|
||||
)
|
||||
@cloud_edition_billing_rate_limit_check("knowledge", "dataset")
|
||||
|
||||
@ -211,7 +211,11 @@ def _non_null_property_schema(property_schema: object) -> dict[str, Any]:
|
||||
]
|
||||
if len(non_null_candidates) == 1:
|
||||
return {
|
||||
**{key: value for key, value in property_schema.items() if key != "anyOf"},
|
||||
**{
|
||||
key: value
|
||||
for key, value in property_schema.items()
|
||||
if key != "anyOf" and not (key == "default" and value is None)
|
||||
},
|
||||
**deepcopy(non_null_candidates[0]),
|
||||
}
|
||||
|
||||
@ -246,7 +250,7 @@ class DocumentGetQuery(BaseModel):
|
||||
default="all",
|
||||
description=(
|
||||
"`all` returns all fields including metadata. `only` returns only `id`, `doc_type`, and "
|
||||
"`doc_metadata`. `without` returns all fields except `doc_metadata`."
|
||||
"`doc_metadata`. `without` returns all fields except `doc_type` and `doc_metadata`."
|
||||
),
|
||||
)
|
||||
|
||||
@ -305,39 +309,53 @@ def _document_and_batch_response(document: Document, batch: str, *, session: Ses
|
||||
)
|
||||
|
||||
|
||||
# Use SkipJsonSchema to support 3 metadata modes
|
||||
def _omit_schema_default(schema: dict[str, Any]) -> None:
|
||||
"""Keep omission placeholders out of the public non-null field contract."""
|
||||
schema.pop("default", None)
|
||||
|
||||
|
||||
# These fields are absent in metadata=only responses. None is an internal
|
||||
# validation default, not a value returned for these fields when present.
|
||||
class DocumentDetailResponse(ResponseModel):
|
||||
id: str
|
||||
position: int | SkipJsonSchema[None] = None
|
||||
data_source_type: str | SkipJsonSchema[None] = None
|
||||
data_source_info: dict[str, Any] | SkipJsonSchema[None] = None
|
||||
position: int | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
data_source_type: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
data_source_info: dict[str, Any] | SkipJsonSchema[None] = Field(
|
||||
default=None, json_schema_extra=_omit_schema_default
|
||||
)
|
||||
dataset_process_rule_id: str | None = None
|
||||
dataset_process_rule: dict[str, Any] | SkipJsonSchema[None] = None
|
||||
document_process_rule: dict[str, Any] | SkipJsonSchema[None] = None
|
||||
name: str | SkipJsonSchema[None] = None
|
||||
created_from: str | SkipJsonSchema[None] = None
|
||||
created_by: str | SkipJsonSchema[None] = None
|
||||
created_at: int | SkipJsonSchema[None] = None
|
||||
dataset_process_rule: dict[str, Any] | SkipJsonSchema[None] = Field(
|
||||
default=None, json_schema_extra=_omit_schema_default
|
||||
)
|
||||
document_process_rule: dict[str, Any] | SkipJsonSchema[None] = Field(
|
||||
default=None, json_schema_extra=_omit_schema_default
|
||||
)
|
||||
name: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
created_from: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
created_by: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
created_at: int | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
tokens: int | None = None
|
||||
indexing_status: str | SkipJsonSchema[None] = None
|
||||
indexing_status: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
completed_at: int | None = None
|
||||
updated_at: int | None = None
|
||||
indexing_latency: float | None = None
|
||||
error: str | None = None
|
||||
enabled: bool | SkipJsonSchema[None] = None
|
||||
enabled: bool | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
disabled_at: int | None = None
|
||||
disabled_by: str | None = None
|
||||
archived: bool | SkipJsonSchema[None] = None
|
||||
archived: bool | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
doc_type: str | None = None
|
||||
doc_metadata: list[DocumentMetadataResponse] | dict[str, Any] | None = None
|
||||
segment_count: int | SkipJsonSchema[None] = None
|
||||
average_segment_length: int | float | SkipJsonSchema[None] = None
|
||||
hit_count: int | SkipJsonSchema[None] = None
|
||||
segment_count: int | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
average_segment_length: int | float | SkipJsonSchema[None] = Field(
|
||||
default=None, json_schema_extra=_omit_schema_default
|
||||
)
|
||||
hit_count: int | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
display_status: str | None = None
|
||||
doc_form: str | SkipJsonSchema[None] = None
|
||||
doc_form: str | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
doc_language: str | None = None
|
||||
summary_index_status: str | None = None
|
||||
need_summary: bool | SkipJsonSchema[None] = None
|
||||
need_summary: bool | SkipJsonSchema[None] = Field(default=None, json_schema_extra=_omit_schema_default)
|
||||
|
||||
@field_validator("data_source_type", "indexing_status", "display_status", "doc_form", mode="before")
|
||||
@classmethod
|
||||
@ -1452,7 +1470,6 @@ class DocumentApi(DatasetApiResource):
|
||||
tags=["Documents"],
|
||||
responses={
|
||||
204: "Success.",
|
||||
400: "`document_indexing` : Cannot delete document during indexing.",
|
||||
403: "`archived_document_immutable` : The archived document is not editable.",
|
||||
404: "`not_found` : Document Not Exists.",
|
||||
},
|
||||
|
||||
@ -16,8 +16,8 @@ from pydantic import BaseModel, WithJsonSchema
|
||||
from libs.flask_restx_compat import BINARY_RESPONSE_MEDIA_TYPES_VENDOR_KEY
|
||||
|
||||
USER_DESCRIPTION = (
|
||||
"User identifier, unique within the application. This identifier scopes data access; resources created with "
|
||||
"one `user` value are only visible when queried with the same `user` value."
|
||||
"End-user identifier, defined by your app and unique within it. Identifies the end user for this request. "
|
||||
"See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules."
|
||||
)
|
||||
SCOPED_TASK_STOP_USER_DESCRIPTION = (
|
||||
"End-user identifier, defined by your app and unique within it. Send the same `user` value used for the original "
|
||||
|
||||
@ -181,7 +181,6 @@ Retrieves the status of an asynchronous annotation reply configuration job start
|
||||
| 400 | `invalid_param` : The specified job does not exist. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | Forbidden - token scope, app, dataset, or workspace access denied | |
|
||||
| 404 | Job not found | |
|
||||
|
||||
### [GET] /apps/annotations
|
||||
**List Annotations**
|
||||
@ -560,7 +559,6 @@ Send a request to the text generation application.
|
||||
| 400 | - `app_unavailable` : App unavailable or misconfigured. - `provider_not_initialize` : No valid model provider credentials found. - `provider_quota_exceeded` : Model provider quota exhausted. - `model_currently_not_support` : Current model unavailable. - `completion_request_error` : Text generation failed. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | Forbidden - token scope, app, dataset, or workspace access denied | |
|
||||
| 404 | Conversation not found | |
|
||||
| 429 | `too_many_requests` : Too many concurrent requests for this app. | |
|
||||
| 500 | `internal_server_error` : Internal server error. | |
|
||||
|
||||
@ -798,7 +796,7 @@ Create a new empty knowledge base. After creation, use [Create Document by Text]
|
||||
### [DELETE] /datasets/{dataset_id}
|
||||
**Delete Knowledge Base**
|
||||
|
||||
Permanently delete a knowledge base and all its documents. The knowledge base must not be in use by any application.
|
||||
Permanently delete a knowledge base and all its documents.
|
||||
|
||||
#### Parameters
|
||||
|
||||
@ -814,7 +812,6 @@ Permanently delete a knowledge base and all its documents. The knowledge base mu
|
||||
| 401 | Unauthorized - invalid API token |
|
||||
| 403 | Forbidden - dataset API access or workspace access denied |
|
||||
| 404 | `not_found` : Dataset not found. |
|
||||
| 409 | `dataset_in_use` : The knowledge base is being used by some apps. Please remove it from the apps before deleting. |
|
||||
|
||||
### [GET] /datasets/{dataset_id}
|
||||
**Get Knowledge Base**
|
||||
@ -1370,7 +1367,6 @@ Permanently delete a document and all its chunks from the knowledge base.
|
||||
| Code | Description |
|
||||
| ---- | ----------- |
|
||||
| 204 | Success. |
|
||||
| 400 | `document_indexing` : Cannot delete document during indexing. |
|
||||
| 401 | Unauthorized - invalid API token |
|
||||
| 403 | `archived_document_immutable` : The archived document is not editable. |
|
||||
| 404 | `not_found` : Document Not Exists. |
|
||||
@ -1386,7 +1382,7 @@ Retrieve detailed information about a specific document, including its indexing
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| dataset_id | path | Knowledge base ID. | Yes | string (uuid) |
|
||||
| document_id | path | Document ID. | Yes | string (uuid) |
|
||||
| metadata | query | `all` returns all fields including metadata. `only` returns only `id`, `doc_type`, and `doc_metadata`. `without` returns all fields except `doc_metadata`. | No | string, <br>**Available values:** "all", "only", "without", <br>**Default:** all |
|
||||
| metadata | query | `all` returns all fields including metadata. `only` returns only `id`, `doc_type`, and `doc_metadata`. `without` returns all fields except `doc_type` and `doc_metadata`. | No | string, <br>**Available values:** "all", "only", "without", <br>**Default:** all |
|
||||
|
||||
#### Responses
|
||||
|
||||
@ -2093,9 +2089,9 @@ Retrieve basic information about this application, including name, description,
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Basic information of the application. | **application/json**: [AppInfoResponse](#appinforesponse)<br> |
|
||||
| 400 | `app_unavailable` : App unavailable or misconfigured. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | Forbidden - token scope, app, dataset, or workspace access denied | |
|
||||
| 404 | Application not found | |
|
||||
|
||||
### [GET] /meta
|
||||
**Get App Meta**
|
||||
@ -2107,9 +2103,9 @@ Retrieve metadata about this application, including tool icons and other configu
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Successfully retrieved application meta information. | **application/json**: [AppMetaResponse](#appmetaresponse)<br> |
|
||||
| 400 | `app_unavailable` : App unavailable or misconfigured. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | Forbidden - token scope, app, dataset, or workspace access denied | |
|
||||
| 404 | Application not found | |
|
||||
|
||||
### [GET] /parameters
|
||||
**Get App Parameters**
|
||||
@ -2124,7 +2120,6 @@ Retrieve the application's input form configuration, including feature switches,
|
||||
| 400 | `app_unavailable` : App unavailable or misconfigured. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | Forbidden - token scope, app, dataset, or workspace access denied | |
|
||||
| 404 | Application not found | |
|
||||
|
||||
### [GET] /site
|
||||
**Get App WebApp Settings**
|
||||
@ -2212,8 +2207,7 @@ Execute a workflow. Cannot be executed without a published workflow.
|
||||
| 400 | - `not_workflow_app` : App mode does not match the API route. - `provider_not_initialize` : No valid model provider credentials found. - `provider_quota_exceeded` : Model provider quota exhausted. - `model_currently_not_support` : Current model unavailable. - `completion_request_error` : Workflow execution request failed. - `invalid_param` : Invalid parameter value. | |
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | - `forbidden` : Token scope, app, or workspace access denied. - `trigger_workflow_service_mode_unavailable` : Trigger-entry workflows cannot be invoked through Web App, Service API, OpenAPI, or MCP. | |
|
||||
| 404 | Workflow not found | |
|
||||
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit was exceeded. | |
|
||||
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution quota was exceeded. | |
|
||||
| 500 | `internal_server_error` : Internal server error. | |
|
||||
|
||||
### [GET] /workflows/run/{workflow_run_id}
|
||||
@ -2289,7 +2283,7 @@ Execute a specific workflow version identified by its ID. Useful for running a p
|
||||
| 401 | Unauthorized - invalid API token | |
|
||||
| 403 | - `forbidden` : Token scope, app, or workspace access denied. - `workflow_version_execution_not_allowed` : Workflow version execution is unavailable on the current plan. Upgrade to a paid plan. - `trigger_workflow_service_mode_unavailable` : The selected workflow version uses a trigger entry and cannot be invoked through Web App, Service API, OpenAPI, or MCP. | |
|
||||
| 404 | `not_found` : Workflow not found. | |
|
||||
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit was exceeded. | |
|
||||
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution quota was exceeded. | |
|
||||
| 500 | `internal_server_error` : Internal server error. | |
|
||||
|
||||
---
|
||||
@ -2600,7 +2594,7 @@ Public pause reason emitted by a blocking Chatflow execution.
|
||||
| inputs | object | Values for app-defined variables. Refer to the `user_input_form` field in the [Get App Parameters](/api-reference/applications/get-app-parameters) response to discover expected variable names and types. | Yes |
|
||||
| query | string | User input or question content. | Yes |
|
||||
| response_mode | string, <br>**Available values:** "blocking", "streaming" | Response mode. `streaming` uses Server-Sent Events; `blocking` returns after completion. New Agent app mode supports streaming only. When omitted, non-Agent apps run in blocking mode and new Agent apps stream. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | Yes |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | Yes |
|
||||
| workflow_id | string | Published workflow version ID to execute for advanced chat. If omitted, the app's current published workflow is used. | No |
|
||||
|
||||
#### ChildChunkCreatePayload
|
||||
@ -2682,7 +2676,7 @@ Public pause reason emitted by a blocking Chatflow execution.
|
||||
| inputs | object | Values for app-defined variables. Refer to the `user_input_form` field in the [Get App Parameters](/api-reference/applications/get-app-parameters) response to discover expected variable names and types. | Yes |
|
||||
| query | string | User input or prompt content. | No |
|
||||
| response_mode | string, <br>**Available values:** "blocking", "streaming" | Response mode. `streaming` uses Server-Sent Events; `blocking` returns after completion. When omitted, the request runs in blocking mode. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | Yes |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | Yes |
|
||||
|
||||
#### Condition
|
||||
|
||||
@ -2723,7 +2717,7 @@ Condition detail
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| auto_generate | boolean | Automatically generate the conversation name. When `true`, the `name` field is ignored. | No |
|
||||
| name | string | Conversation name. Required when `auto_generate` is `false`. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | No |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | No |
|
||||
|
||||
#### ConversationVariableInfiniteScrollPaginationResponse
|
||||
|
||||
@ -2755,7 +2749,7 @@ Condition detail
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | No |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | No |
|
||||
| value | | The new value for the variable. Must match the variable's expected type. | Yes |
|
||||
|
||||
#### ConversationVariablesQuery
|
||||
@ -2833,7 +2827,6 @@ Enum class for custom configuration status.
|
||||
| maintainer | string | | No |
|
||||
| name | string | | Yes |
|
||||
| permission | string | | Yes |
|
||||
| permission_keys | [ string ] | | No |
|
||||
| pipeline_id | string | | Yes |
|
||||
| provider | string | | Yes |
|
||||
| retrieval_model_dict | [DatasetRetrievalModelResponse](#datasetretrievalmodelresponse) | Retrieval configuration for the knowledge base. | Yes |
|
||||
@ -2876,7 +2869,6 @@ Enum class for custom configuration status.
|
||||
| name | string | | Yes |
|
||||
| partial_member_list | [ string ] | | No |
|
||||
| permission | string | | Yes |
|
||||
| permission_keys | [ string ] | | No |
|
||||
| pipeline_id | string | | Yes |
|
||||
| provider | string | | Yes |
|
||||
| retrieval_model_dict | [DatasetRetrievalModelResponse](#datasetretrievalmodelresponse) | Retrieval configuration for the knowledge base. | Yes |
|
||||
@ -3158,7 +3150,7 @@ Request payload for bulk downloading documents as a zip archive.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| metadata | string, <br>**Available values:** "all", "only", "without", <br>**Default:** all | `all` returns all fields including metadata. `only` returns only `id`, `doc_type`, and `doc_metadata`. `without` returns all fields except `doc_metadata`.<br>*Enum:* `"all"`, `"only"`, `"without"` | No |
|
||||
| metadata | string, <br>**Available values:** "all", "only", "without", <br>**Default:** all | `all` returns all fields including metadata. `only` returns only `id`, `doc_type`, and `doc_metadata`. `without` returns all fields except `doc_type` and `doc_metadata`.<br>*Enum:* `"all"`, `"only"`, `"without"` | No |
|
||||
|
||||
#### DocumentListQuery
|
||||
|
||||
@ -3546,7 +3538,7 @@ Enum class for fetch from.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| action | string | ID of the action button the recipient selected. Must match one of the `id` values from the form's `user_actions` list. | Yes |
|
||||
| inputs | object | Submitted human input values keyed by output variable name. Use a string for paragraph or select input values, a file mapping for file inputs, and a list of file mappings for file-list inputs. Local file mappings use `transfer_method=local_file` with `upload_file_id`; remote file mappings use `transfer_method=remote_url` with `url` or `remote_url`. | Yes |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | Yes |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | Yes |
|
||||
|
||||
#### HumanInputFormSubmitResponse
|
||||
|
||||
@ -3622,7 +3614,7 @@ Model class for i18n object.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| content | string | Optional text feedback providing additional detail. | No |
|
||||
| rating | string, <br>**Available values:** "dislike", "like" | Feedback rating. Set to `null` to revoke previously submitted feedback. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | Yes |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | Yes |
|
||||
|
||||
#### MessageFile
|
||||
|
||||
@ -3753,7 +3745,7 @@ Enum class for model type.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | No |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | No |
|
||||
|
||||
#### ParagraphInputConfig
|
||||
|
||||
@ -4253,7 +4245,7 @@ Accepts either the legacy tag_id payload or the normalized tag_ids payload.
|
||||
| message_id | string | Message ID. Takes priority over `text` when both are provided. | No |
|
||||
| streaming | boolean | Reserved for compatibility; TTS response streaming is determined by the provider output. | No |
|
||||
| text | string | Speech content to convert. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | No |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | No |
|
||||
| voice | string | Voice to use for text-to-speech. Available voices depend on the TTS provider configured for this app. Omit to use the app's configured voice when available; that value is exposed by [Get App Parameters](/api-reference/applications/get-app-parameters) as `text_to_speech.voice`. | No |
|
||||
|
||||
#### ToolIcon
|
||||
@ -4461,7 +4453,7 @@ Public pause reason emitted by a blocking Workflow execution.
|
||||
| files | [ object<br>object<br>object<br>object ] | File list for workflow system file inputs. Available when file upload is enabled for the workflow. To attach a local file, first upload it via [Upload File](/api-reference/files/upload-file) and use the returned `id` as `upload_file_id` with `transfer_method: local_file`. | No |
|
||||
| inputs | object | Key-value pairs for workflow input variables. Values for file-type variables should be arrays of file objects with `type`, `transfer_method`, and either `url` or `upload_file_id`. Refer to the `user_input_form` field in the [Get App Parameters](/api-reference/applications/get-app-parameters) response to discover the variable names and types expected by your app. | Yes |
|
||||
| response_mode | string, <br>**Available values:** "blocking", "streaming" | Response mode. Use `blocking` for synchronous responses or `streaming` for Server-Sent Events. When omitted, the request runs in blocking mode. | No |
|
||||
| user | string | User identifier, unique within the application. This identifier scopes data access; resources created with one `user` value are only visible when queried with the same `user` value. | Yes |
|
||||
| user | string | End-user identifier, defined by your app and unique within it. Identifies the end user for this request. See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules. | Yes |
|
||||
|
||||
#### WorkflowRunResponse
|
||||
|
||||
|
||||
@ -195,6 +195,19 @@ def test_generate_specs_writes_openapi_with_resolvable_references_and_null_defau
|
||||
assert "default" in conversation_id
|
||||
assert conversation_id["default"] is None
|
||||
|
||||
schemas = service_payload["components"]["schemas"]
|
||||
document_detail = schemas["DocumentDetailResponse"]
|
||||
validator = Draft202012Validator(service_payload)
|
||||
for schema in (document_detail, *schemas["DocumentTextUpdate"]["anyOf"]):
|
||||
for property_schema in schema["properties"].values():
|
||||
if "default" in property_schema:
|
||||
validator.evolve(schema=property_schema).validate(property_schema["default"])
|
||||
|
||||
assert document_detail["required"] == ["id"]
|
||||
assert document_detail["properties"]["enabled"]["type"] == "boolean"
|
||||
assert "default" not in document_detail["properties"]["enabled"]
|
||||
assert document_detail["properties"]["tokens"]["default"] is None
|
||||
|
||||
|
||||
def test_generate_specs_writes_unique_operation_ids(tmp_path: Path):
|
||||
module = _load_generate_swagger_specs_module()
|
||||
|
||||
@ -15,8 +15,8 @@ def _swagger_config(config_overrides) -> None:
|
||||
|
||||
USER_PROPERTY_SCHEMA = {
|
||||
"description": (
|
||||
"User identifier, unique within the application. This identifier scopes data access; resources created with "
|
||||
"one `user` value are only visible when queried with the same `user` value."
|
||||
"End-user identifier, defined by your app and unique within it. Identifies the end user for this request. "
|
||||
"See [End User Identity](/api-reference/guides/end-user-identity) for endpoint-specific access rules."
|
||||
),
|
||||
"type": "string",
|
||||
}
|
||||
@ -298,6 +298,11 @@ def test_service_openapi_documents_decorator_user_contracts():
|
||||
assert schema["properties"]["user"] == USER_PROPERTY_SCHEMA
|
||||
assert "user" in schema["required"]
|
||||
|
||||
for path in ("/workflows/run", "/workflows/{workflow_id}/run"):
|
||||
rate_limit_description = paths[path]["post"]["responses"]["429"]["description"]
|
||||
assert "upstream model provider rate limit" in rate_limit_description
|
||||
assert "Dify Cloud workflow execution quota" in rate_limit_description
|
||||
|
||||
task_stop_user_descriptions = {
|
||||
"/completion-messages/{task_id}/stop": "Send the same",
|
||||
"/chat-messages/{task_id}/stop": "Send the same",
|
||||
@ -477,10 +482,75 @@ def test_service_openapi_documents_conditional_payload_schemas():
|
||||
with_text_branch, without_text_branch = document_update_schema["anyOf"]
|
||||
assert with_text_branch["properties"]["text"]["type"] == "string"
|
||||
assert with_text_branch["properties"]["name"]["type"] == "string"
|
||||
assert "default" not in with_text_branch["properties"]["text"]
|
||||
assert "default" not in with_text_branch["properties"]["name"]
|
||||
assert with_text_branch["required"] == ["name", "text"]
|
||||
assert without_text_branch["properties"]["text"]["type"] == "null"
|
||||
|
||||
|
||||
def test_service_dataset_response_schemas_omit_console_permission_metadata():
|
||||
from controllers.console import bp as console_bp
|
||||
from controllers.service_api import bp as service_api_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["TESTING"] = True
|
||||
app.config["RESTX_INCLUDE_ALL_MODELS"] = True
|
||||
app.register_blueprint(console_bp)
|
||||
app.register_blueprint(service_api_bp)
|
||||
|
||||
service_payload = app.test_client().get("/v1/openapi.json").get_json()
|
||||
service_schemas = service_payload["components"]["schemas"]
|
||||
for name in ("DatasetDetailResponse", "DatasetDetailWithPartialMembersResponse"):
|
||||
assert "permission_keys" not in service_schemas[name]["properties"]
|
||||
assert service_schemas["DatasetListResponse"]["properties"]["data"]["items"] == {
|
||||
"$ref": "#/components/schemas/DatasetDetailResponse"
|
||||
}
|
||||
|
||||
console_payload = app.test_client().get("/console/api/openapi.json").get_json()
|
||||
console_schema = console_payload["components"]["schemas"]["DatasetDetailResponse"]
|
||||
assert "permission_keys" in console_schema["properties"]
|
||||
|
||||
|
||||
def test_service_delete_schemas_omit_unenforced_state_constraints():
|
||||
from controllers.service_api import bp as service_api_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["TESTING"] = True
|
||||
app.register_blueprint(service_api_bp)
|
||||
paths = app.test_client().get("/v1/openapi.json").get_json()["paths"]
|
||||
|
||||
delete_dataset = paths["/datasets/{dataset_id}"]["delete"]
|
||||
assert "409" not in delete_dataset["responses"]
|
||||
assert "must not be in use" not in delete_dataset["description"]
|
||||
|
||||
delete_document = paths["/datasets/{dataset_id}/documents/{document_id}"]["delete"]
|
||||
assert "document_indexing" not in json.dumps(delete_document["responses"])
|
||||
assert "archived_document_immutable" in delete_document["responses"]["403"]["description"]
|
||||
|
||||
|
||||
def test_service_schemas_only_document_reachable_not_found_responses():
|
||||
from controllers.service_api import bp as service_api_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["TESTING"] = True
|
||||
app.register_blueprint(service_api_bp)
|
||||
paths = app.test_client().get("/v1/openapi.json").get_json()["paths"]
|
||||
|
||||
for path, method in (
|
||||
("/apps/annotation-reply/{action}/status/{job_id}", "get"),
|
||||
("/info", "get"),
|
||||
("/meta", "get"),
|
||||
("/parameters", "get"),
|
||||
("/workflows/run", "post"),
|
||||
("/completion-messages", "post"),
|
||||
):
|
||||
assert "404" not in paths[path][method]["responses"]
|
||||
assert "400" in paths[path][method]["responses"]
|
||||
|
||||
assert "404" in paths["/workflows/{workflow_id}/run"]["post"]["responses"]
|
||||
assert "404" in paths["/chat-messages"]["post"]["responses"]
|
||||
|
||||
|
||||
def test_service_openapi_does_not_encode_docs_coverage_boundaries():
|
||||
from controllers.service_api import bp as service_api_bp
|
||||
|
||||
|
||||
@ -1888,12 +1888,11 @@ export const tags2 = {
|
||||
/**
|
||||
* Delete Knowledge Base
|
||||
*
|
||||
* Permanently delete a knowledge base and all its documents. The knowledge base must not be in use by any application.
|
||||
* Permanently delete a knowledge base and all its documents.
|
||||
*/
|
||||
export const delete8 = oc
|
||||
.route({
|
||||
description:
|
||||
'Permanently delete a knowledge base and all its documents. The knowledge base must not be in use by any application.',
|
||||
description: 'Permanently delete a knowledge base and all its documents.',
|
||||
inputStructure: 'detailed',
|
||||
method: 'DELETE',
|
||||
operationId: 'deleteDatasetsByDatasetId',
|
||||
|
||||
@ -585,7 +585,6 @@ export type DatasetDetailResponse = {
|
||||
maintainer?: string | null
|
||||
name: string
|
||||
permission: string
|
||||
permission_keys?: Array<string>
|
||||
pipeline_id: string | null
|
||||
provider: string
|
||||
retrieval_model_dict: DatasetRetrievalModelResponse
|
||||
@ -626,7 +625,6 @@ export type DatasetDetailWithPartialMembersResponse = {
|
||||
name: string
|
||||
partial_member_list?: Array<string> | null
|
||||
permission: string
|
||||
permission_keys?: Array<string>
|
||||
pipeline_id: string | null
|
||||
provider: string
|
||||
retrieval_model_dict: DatasetRetrievalModelResponse
|
||||
@ -2492,7 +2490,6 @@ export type GetAppsAnnotationReplyByActionStatusByJobIdErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetAppsAnnotationReplyByActionStatusByJobIdResponses = {
|
||||
@ -2668,7 +2665,6 @@ export type PostCompletionMessagesErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
429: unknown
|
||||
500: unknown
|
||||
}
|
||||
@ -3023,7 +3019,6 @@ export type DeleteDatasetsByDatasetIdErrors = {
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
409: unknown
|
||||
}
|
||||
|
||||
export type DeleteDatasetsByDatasetIdResponses = {
|
||||
@ -3312,7 +3307,6 @@ export type DeleteDatasetsByDatasetIdDocumentsByDocumentIdData = {
|
||||
}
|
||||
|
||||
export type DeleteDatasetsByDatasetIdDocumentsByDocumentIdErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
@ -4162,9 +4156,9 @@ export type GetInfoData = {
|
||||
}
|
||||
|
||||
export type GetInfoErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetInfoResponses = {
|
||||
@ -4255,9 +4249,9 @@ export type GetMetaData = {
|
||||
}
|
||||
|
||||
export type GetMetaErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetMetaResponses = {
|
||||
@ -4277,7 +4271,6 @@ export type GetParametersErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetParametersResponses = {
|
||||
@ -4390,7 +4383,6 @@ export type PostWorkflowsRunErrors = {
|
||||
400: unknown
|
||||
401: unknown
|
||||
403: unknown
|
||||
404: unknown
|
||||
429: unknown
|
||||
500: unknown
|
||||
}
|
||||
|
||||
@ -784,7 +784,6 @@ export const zDatasetDetailResponse = z.object({
|
||||
maintainer: z.string().nullish(),
|
||||
name: z.string(),
|
||||
permission: z.string(),
|
||||
permission_keys: z.array(z.string()).optional(),
|
||||
pipeline_id: z.string().nullable(),
|
||||
provider: z.string(),
|
||||
retrieval_model_dict: zDatasetRetrievalModelResponse,
|
||||
@ -828,7 +827,6 @@ export const zDatasetDetailWithPartialMembersResponse = z.object({
|
||||
name: z.string(),
|
||||
partial_member_list: z.array(z.string()).nullish(),
|
||||
permission: z.string(),
|
||||
permission_keys: z.array(z.string()).optional(),
|
||||
pipeline_id: z.string().nullable(),
|
||||
provider: z.string(),
|
||||
retrieval_model_dict: zDatasetRetrievalModelResponse,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user