Compare commits
2 Commits
codex/refa
...
codex/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f6f8d1978 | ||
|
|
5d56b168d2 |
@@ -159,12 +159,8 @@ class AgentBackendModelConfig(BaseModel):
|
||||
# ``DifyPluginLLMLayerConfig.model_settings`` is pydantic_ai's ``ModelSettings``
|
||||
# TypedDict (closed: unknown keys are rejected, explicit ``None`` values fail the
|
||||
# per-field type checks). Agent Soul model settings carry a wider, nullable shape
|
||||
# (``stop`` / ``response_format`` plus null-padded fields, plus arbitrary
|
||||
# plugin-declared parameters such as Qwen's ``enable_thinking``), so the layer
|
||||
# config only receives the keys the runtime contract accepts directly; anything
|
||||
# else is forwarded through ``extra_body``, the TypedDict's own escape hatch for
|
||||
# provider-specific parameters (see
|
||||
# ``dify_agent.adapters.llm.model._map_model_settings_to_parameters``).
|
||||
# (``stop`` / ``response_format`` plus null-padded fields), so the layer config
|
||||
# only receives the keys the runtime contract accepts.
|
||||
_AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS = (
|
||||
"temperature",
|
||||
"top_p",
|
||||
@@ -172,7 +168,6 @@ _AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS = (
|
||||
"frequency_penalty",
|
||||
"max_tokens",
|
||||
)
|
||||
_AGENT_MODEL_SETTINGS_KNOWN_KEYS = frozenset({*_AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS, "stop", "response_format"})
|
||||
|
||||
|
||||
def _agent_model_settings(settings: Mapping[str, JsonValue]) -> dict[str, JsonValue] | None:
|
||||
@@ -182,15 +177,6 @@ def _agent_model_settings(settings: Mapping[str, JsonValue]) -> dict[str, JsonVa
|
||||
stop = settings.get("stop")
|
||||
if isinstance(stop, list) and stop:
|
||||
sanitized["stop_sequences"] = stop
|
||||
|
||||
extra_body: dict[str, JsonValue] = {
|
||||
key: value
|
||||
for key, value in settings.items()
|
||||
if key not in _AGENT_MODEL_SETTINGS_KNOWN_KEYS and value is not None
|
||||
}
|
||||
if extra_body:
|
||||
sanitized["extra_body"] = extra_body
|
||||
|
||||
return sanitized or None
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -9,7 +10,7 @@ from controllers.common.schema import (
|
||||
register_response_schema_models,
|
||||
)
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, setup_required
|
||||
from controllers.console.wraps import account_initialization_required, setup_required
|
||||
from fields.base import ResponseModel
|
||||
from libs.login import login_required
|
||||
from services.advanced_prompt_template_service import AdvancedPromptTemplateArgs, AdvancedPromptTemplateService
|
||||
@@ -48,12 +49,12 @@ class AdvancedPromptTemplateList(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@model_validate(AdvancedPromptTemplateQuery)
|
||||
def get(self, req_data: AdvancedPromptTemplateQuery):
|
||||
def get(self):
|
||||
args = AdvancedPromptTemplateQuery.model_validate(request.args.to_dict(flat=True))
|
||||
prompt_args: AdvancedPromptTemplateArgs = {
|
||||
"app_mode": req_data.app_mode,
|
||||
"model_mode": req_data.model_mode,
|
||||
"model_name": req_data.model_name,
|
||||
"has_context": req_data.has_context,
|
||||
"app_mode": args.app_mode,
|
||||
"model_mode": args.model_mode,
|
||||
"model_name": args.model_name,
|
||||
"has_context": args.has_context,
|
||||
}
|
||||
return AdvancedPromptTemplateService.get_prompt(prompt_args)
|
||||
|
||||
@@ -21,7 +21,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -212,12 +211,12 @@ def _upload_skill_for_app(*, session: Session, current_user: Account, app_model:
|
||||
|
||||
|
||||
def _commit_drive_file_for_app(*, session: Session, current_user: Account, app_model: App, allow_node_id: bool = True):
|
||||
payload = AgentDriveFilePayload.model_validate(console_ns.payload or {})
|
||||
query = query_params_from_request(AgentDriveMutationQuery)
|
||||
node_id = query.node_id if allow_node_id else None
|
||||
agent_id = _resolve_agent_id(session, app_model, node_id)
|
||||
if not agent_id:
|
||||
return _agent_not_bound()
|
||||
payload = AgentDriveFilePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
upload_file = session.scalar(
|
||||
select(UploadFile).where(
|
||||
@@ -342,11 +341,11 @@ class AgentLogApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@with_session(write=False)
|
||||
@get_app_model(mode=[AppMode.AGENT_CHAT])
|
||||
@model_validate(AgentLogQuery)
|
||||
def get(self, req_data: AgentLogQuery, session: Session, app_model: App):
|
||||
def get(self, session: Session, app_model: App):
|
||||
"""Get agent logs"""
|
||||
args = AgentLogQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
return AgentService.get_agent_logs(app_model, req_data.conversation_id, req_data.message_id, session)
|
||||
return AgentService.get_agent_logs(app_model, args.conversation_id, args.message_id, session)
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/skills/upload")
|
||||
|
||||
@@ -25,7 +25,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -88,21 +87,14 @@ class AgentAppFeatureConfigResource(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(AgentAppFeaturesPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: AgentAppFeaturesPayload,
|
||||
session: Session,
|
||||
tenant_id: str,
|
||||
current_user: Account,
|
||||
agent_id: UUID,
|
||||
):
|
||||
def post(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
app_model = resolve_agent_runtime_app_model(session=session, tenant_id=tenant_id, agent_id=agent_id)
|
||||
args = AgentAppFeaturesPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
new_app_model_config = AgentAppFeatureConfigService.update_features(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
config=req_data.model_dump(exclude_none=True),
|
||||
config=args.model_dump(exclude_none=True),
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import AliasChoices, BaseModel, Field, ValidationInfo, computed_field, field_validator, model_validator
|
||||
from sqlalchemy import select
|
||||
@@ -32,7 +33,6 @@ from controllers.console.wraps import (
|
||||
edit_permission_required,
|
||||
enterprise_license_required,
|
||||
is_admin_or_owner_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -697,16 +697,16 @@ class AppListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(CreateAppPayload)
|
||||
def post(self, req_data: CreateAppPayload, session: Session, current_tenant_id: str, current_user: Account):
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
"""Create app"""
|
||||
args = CreateAppPayload.model_validate(console_ns.payload)
|
||||
params = CreateAppParams(
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
mode=req_data.mode,
|
||||
icon_type=req_data.icon_type,
|
||||
icon=req_data.icon,
|
||||
icon_background=req_data.icon_background,
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
mode=args.mode,
|
||||
icon_type=args.icon_type,
|
||||
icon=args.icon,
|
||||
icon_background=args.icon_background,
|
||||
)
|
||||
|
||||
app_service = AppService()
|
||||
@@ -908,20 +908,20 @@ class AppApi(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(UpdateAppPayload)
|
||||
def put(self, req_data: UpdateAppPayload, session: Session, app_model: App):
|
||||
def put(self, session: Session, app_model: App):
|
||||
"""Update app"""
|
||||
args = UpdateAppPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
|
||||
args_dict: AppService.ArgsDict = {
|
||||
"name": req_data.name,
|
||||
"description": req_data.description or "",
|
||||
"icon_type": req_data.icon_type,
|
||||
"icon": req_data.icon or "",
|
||||
"icon_background": req_data.icon_background or "",
|
||||
"use_icon_as_answer_icon": req_data.use_icon_as_answer_icon or False,
|
||||
"max_active_requests": req_data.max_active_requests or 0,
|
||||
"name": args.name,
|
||||
"description": args.description or "",
|
||||
"icon_type": args.icon_type,
|
||||
"icon": args.icon or "",
|
||||
"icon_background": args.icon_background or "",
|
||||
"use_icon_as_answer_icon": args.use_icon_as_answer_icon or False,
|
||||
"max_active_requests": args.max_active_requests or 0,
|
||||
}
|
||||
app_model = app_service.update_app(app_model, args_dict, session=session)
|
||||
return AppDetailWithSite.model_validate(
|
||||
@@ -969,10 +969,10 @@ class AppCopyApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(CopyAppPayload)
|
||||
def post(self, req_data: CopyAppPayload, current_tenant_id: str, current_user: Account, app_model: App):
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App):
|
||||
"""Copy app"""
|
||||
# The role of the current user in the ta table must be admin, owner, or editor
|
||||
args = CopyAppPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
import_service = AppDslService(session)
|
||||
@@ -982,11 +982,11 @@ class AppCopyApi(Resource):
|
||||
account=current_user,
|
||||
import_mode=ImportMode.YAML_CONTENT,
|
||||
yaml_content=yaml_content,
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
icon_type=req_data.icon_type,
|
||||
icon=req_data.icon,
|
||||
icon_background=req_data.icon_background,
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
icon_type=args.icon_type,
|
||||
icon=args.icon,
|
||||
icon_background=args.icon_background,
|
||||
)
|
||||
except NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
@@ -1045,16 +1045,16 @@ class AppExportApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL)
|
||||
@agent_manage_required_for_agent_app
|
||||
@get_app_model
|
||||
@model_validate(AppExportQuery)
|
||||
def get(self, req_data: AppExportQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""Export app"""
|
||||
args = AppExportQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
response = AppExportResponse(
|
||||
data=AppDslService.export_dsl(
|
||||
app_model=app_model,
|
||||
session=db.session(),
|
||||
include_secret=req_data.include_secret,
|
||||
workflow_id=req_data.workflow_id,
|
||||
include_secret=args.include_secret,
|
||||
workflow_id=args.workflow_id,
|
||||
)
|
||||
)
|
||||
return response.model_dump(mode="json")
|
||||
@@ -1102,11 +1102,11 @@ class AppNameApi(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(AppNamePayload)
|
||||
def post(self, req_data: AppNamePayload, session: Session, app_model: App):
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppNamePayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_name(app_model, req_data.name, session=session)
|
||||
app_model = app_service.update_app_name(app_model, args.name, session=session)
|
||||
return AppDetail.model_validate(
|
||||
app_model,
|
||||
from_attributes=True,
|
||||
@@ -1130,15 +1130,15 @@ class AppIconApi(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(AppIconPayload)
|
||||
def post(self, req_data: AppIconPayload, session: Session, app_model: App):
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppIconPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_icon(
|
||||
app_model,
|
||||
req_data.icon or "",
|
||||
req_data.icon_background or "",
|
||||
req_data.icon_type,
|
||||
args.icon or "",
|
||||
args.icon_background or "",
|
||||
args.icon_type,
|
||||
session=session,
|
||||
)
|
||||
return AppDetail.model_validate(
|
||||
@@ -1164,11 +1164,11 @@ class AppSiteStatus(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(AppSiteStatusPayload)
|
||||
def post(self, req_data: AppSiteStatusPayload, session: Session, app_model: App):
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppSiteStatusPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_site_status(app_model, req_data.enable_site, session=session)
|
||||
app_model = app_service.update_app_site_status(app_model, args.enable_site, session=session)
|
||||
return AppDetail.model_validate(
|
||||
app_model,
|
||||
from_attributes=True,
|
||||
@@ -1192,11 +1192,11 @@ class AppApiStatus(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
@model_validate(AppApiStatusPayload)
|
||||
def post(self, req_data: AppApiStatusPayload, session: Session, app_model: App):
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppApiStatusPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_api_status(app_model, req_data.enable_api, session=session)
|
||||
app_model = app_service.update_app_api_status(app_model, args.enable_api, session=session)
|
||||
return AppDetail.model_validate(
|
||||
app_model,
|
||||
from_attributes=True,
|
||||
@@ -1242,14 +1242,14 @@ class AppTraceApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TRACING_CONFIG)
|
||||
@get_app_model
|
||||
@model_validate(AppTracePayload)
|
||||
def post(self, req_data: AppTracePayload, app_model: App):
|
||||
def post(self, app_model: App):
|
||||
# add app trace
|
||||
args = AppTracePayload.model_validate(console_ns.payload)
|
||||
|
||||
OpsTraceManager.update_app_tracing_config(
|
||||
app_id=app_model.id,
|
||||
enabled=req_data.enabled,
|
||||
tracing_provider=req_data.tracing_provider,
|
||||
enabled=args.enabled,
|
||||
tracing_provider=args.tracing_provider,
|
||||
)
|
||||
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
|
||||
@@ -12,7 +12,6 @@ from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
cloud_edition_billing_resource_check,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -84,8 +83,8 @@ class AppImportApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL, resource_required=False)
|
||||
@with_current_user
|
||||
@model_validate(AppImportPayload)
|
||||
def post(self, req_data: AppImportPayload, current_user: Account | None = None):
|
||||
def post(self, current_user: Account | None = None):
|
||||
args = AppImportPayload.model_validate(console_ns.payload)
|
||||
current_user = current_user if current_user is not None else _current_user_and_tenant_id(None)[0]
|
||||
|
||||
# AppDslService performs internal commits for some creation paths, so use a plain
|
||||
@@ -97,15 +96,15 @@ class AppImportApi(Resource):
|
||||
try:
|
||||
result = import_service.import_app(
|
||||
account=account,
|
||||
import_mode=req_data.mode,
|
||||
yaml_content=req_data.yaml_content,
|
||||
yaml_url=req_data.yaml_url,
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
icon_type=req_data.icon_type,
|
||||
icon=req_data.icon,
|
||||
icon_background=req_data.icon_background,
|
||||
app_id=req_data.app_id,
|
||||
import_mode=args.mode,
|
||||
yaml_content=args.yaml_content,
|
||||
yaml_url=args.yaml_url,
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
icon_type=args.icon_type,
|
||||
icon=args.icon,
|
||||
icon_background=args.icon_background,
|
||||
app_id=args.app_id,
|
||||
)
|
||||
except NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
@@ -114,7 +113,7 @@ class AppImportApi(Resource):
|
||||
else:
|
||||
session.commit()
|
||||
|
||||
is_created_app = req_data.app_id is None and result.status in {
|
||||
is_created_app = args.app_id is None and result.status in {
|
||||
ImportStatus.COMPLETED,
|
||||
ImportStatus.COMPLETED_WITH_WARNINGS,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -277,15 +276,15 @@ class ChatMessageTextApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model
|
||||
@model_validate(TextToSpeechPayload)
|
||||
def post(self, req_data: TextToSpeechPayload, app_model: App):
|
||||
def post(self, app_model: App):
|
||||
try:
|
||||
payload = TextToSpeechPayload.model_validate(console_ns.payload)
|
||||
message_ref = None
|
||||
if req_data.message_id:
|
||||
if payload.message_id:
|
||||
app_ref = AppRefService.create_app_ref(app_model)
|
||||
message_ref = AppRefService.create_message_ref(
|
||||
app_ref,
|
||||
req_data.message_id,
|
||||
payload.message_id,
|
||||
account_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -293,8 +292,8 @@ class ChatMessageTextApi(Resource):
|
||||
return AudioService.transcript_tts(
|
||||
app_model=app_model,
|
||||
session=db.session(),
|
||||
text=req_data.text,
|
||||
voice=req_data.voice,
|
||||
text=payload.text,
|
||||
voice=payload.voice,
|
||||
message_ref=message_ref,
|
||||
is_draft=True,
|
||||
)
|
||||
@@ -340,12 +339,13 @@ class TextModesApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@get_app_model
|
||||
@model_validate(TextToSpeechVoiceQuery)
|
||||
def get(self, req_data: TextToSpeechVoiceQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
try:
|
||||
args = TextToSpeechVoiceQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
response = AudioService.transcript_tts_voices(
|
||||
tenant_id=app_model.tenant_id,
|
||||
language=req_data.language,
|
||||
language=args.language,
|
||||
)
|
||||
|
||||
return dump_response(TextToSpeechVoiceListResponse, response)
|
||||
|
||||
@@ -29,7 +29,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -161,11 +160,11 @@ class CompletionMessageApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
||||
@with_session
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@model_validate(CompletionMessagePayload)
|
||||
def post(self, req_data: CompletionMessagePayload, session: Session, current_user: Account, app_model: App):
|
||||
args = req_data.model_dump(exclude_none=True, by_alias=True)
|
||||
def post(self, session: Session, current_user: Account, app_model: App):
|
||||
args_model = CompletionMessagePayload.model_validate(console_ns.payload)
|
||||
args = args_model.model_dump(exclude_none=True, by_alias=True)
|
||||
|
||||
streaming = req_data.response_mode != "blocking"
|
||||
streaming = args_model.response_mode != "blocking"
|
||||
args["auto_generate_name"] = False
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
import sqlalchemy as sa
|
||||
from flask import abort
|
||||
from flask import abort, request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy import func, or_
|
||||
@@ -18,7 +18,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -109,17 +108,17 @@ class CompletionConversationApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@with_session(write=False)
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@model_validate(CompletionConversationQuery)
|
||||
def get(self, req_data: CompletionConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
def get(self, session: Session, current_user: Account, app_model: App):
|
||||
args = CompletionConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
query = sa.select(Conversation).where(
|
||||
Conversation.app_id == app_model.id, Conversation.mode == "completion", Conversation.is_deleted.is_(False)
|
||||
)
|
||||
|
||||
if req_data.keyword:
|
||||
if args.keyword:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
escaped_keyword = escape_like_pattern(req_data.keyword)
|
||||
escaped_keyword = escape_like_pattern(args.keyword)
|
||||
query = query.join(Message, Message.conversation_id == Conversation.id).where(
|
||||
or_(
|
||||
Message.query.ilike(f"%{escaped_keyword}%", escape="\\"),
|
||||
@@ -131,7 +130,7 @@ class CompletionConversationApi(Resource):
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -143,7 +142,7 @@ class CompletionConversationApi(Resource):
|
||||
query = query.where(Conversation.created_at < end_datetime_utc)
|
||||
|
||||
# FIXME, the type ignore in this file
|
||||
if req_data.annotation_status == "annotated":
|
||||
if args.annotation_status == "annotated":
|
||||
query = (
|
||||
query.options(selectinload(Conversation.message_annotations)) # type: ignore[arg-type]
|
||||
.join( # type: ignore
|
||||
@@ -151,7 +150,7 @@ class CompletionConversationApi(Resource):
|
||||
)
|
||||
.group_by(Conversation.id)
|
||||
)
|
||||
elif req_data.annotation_status == "not_annotated":
|
||||
elif args.annotation_status == "not_annotated":
|
||||
query = (
|
||||
query.outerjoin(MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id)
|
||||
.group_by(Conversation.id)
|
||||
@@ -160,7 +159,7 @@ class CompletionConversationApi(Resource):
|
||||
|
||||
query = query.order_by(Conversation.created_at.desc())
|
||||
|
||||
conversations = paginate_query(query, session=session, page=req_data.page, per_page=req_data.limit)
|
||||
conversations = paginate_query(query, session=session, page=args.page, per_page=args.limit)
|
||||
|
||||
return dump_response(
|
||||
ConversationPaginationResponse,
|
||||
@@ -239,8 +238,8 @@ class ChatConversationApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@with_session(write=False)
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@model_validate(ChatConversationQuery)
|
||||
def get(self, req_data: ChatConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
def get(self, session: Session, current_user: Account, app_model: App):
|
||||
args = ChatConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
subquery = (
|
||||
sa.select(Conversation.id.label("conversation_id"), EndUser.session_id.label("from_end_user_session_id"))
|
||||
@@ -250,10 +249,10 @@ class ChatConversationApi(Resource):
|
||||
|
||||
query = sa.select(Conversation).where(Conversation.app_id == app_model.id, Conversation.is_deleted.is_(False))
|
||||
|
||||
if req_data.keyword:
|
||||
if args.keyword:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
escaped_keyword = escape_like_pattern(req_data.keyword)
|
||||
escaped_keyword = escape_like_pattern(args.keyword)
|
||||
keyword_filter = f"%{escaped_keyword}%"
|
||||
query = (
|
||||
query.join(
|
||||
@@ -277,12 +276,12 @@ class ChatConversationApi(Resource):
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
if start_datetime_utc:
|
||||
match req_data.sort_by:
|
||||
match args.sort_by:
|
||||
case "updated_at" | "-updated_at":
|
||||
query = query.where(Conversation.updated_at >= start_datetime_utc)
|
||||
case "created_at" | "-created_at" | _:
|
||||
@@ -290,13 +289,13 @@ class ChatConversationApi(Resource):
|
||||
|
||||
if end_datetime_utc:
|
||||
end_datetime_utc = end_datetime_utc.replace(second=59)
|
||||
match req_data.sort_by:
|
||||
match args.sort_by:
|
||||
case "updated_at" | "-updated_at":
|
||||
query = query.where(Conversation.updated_at <= end_datetime_utc)
|
||||
case "created_at" | "-created_at" | _:
|
||||
query = query.where(Conversation.created_at <= end_datetime_utc)
|
||||
|
||||
match req_data.annotation_status:
|
||||
match args.annotation_status:
|
||||
case "annotated":
|
||||
query = (
|
||||
query.options(selectinload(Conversation.message_annotations)) # type: ignore[arg-type]
|
||||
@@ -317,7 +316,7 @@ class ChatConversationApi(Resource):
|
||||
if app_model.mode == AppMode.ADVANCED_CHAT:
|
||||
query = query.where(Conversation.invoke_from != InvokeFrom.DEBUGGER)
|
||||
|
||||
match req_data.sort_by:
|
||||
match args.sort_by:
|
||||
case "created_at":
|
||||
query = query.order_by(Conversation.created_at.asc())
|
||||
case "-created_at":
|
||||
@@ -329,7 +328,7 @@ class ChatConversationApi(Resource):
|
||||
case _:
|
||||
query = query.order_by(Conversation.created_at.desc())
|
||||
|
||||
conversations = paginate_query(query, session=session, page=req_data.page, per_page=req_data.limit)
|
||||
conversations = paginate_query(query, session=session, page=args.page, per_page=args.limit)
|
||||
|
||||
return dump_response(
|
||||
ConversationWithSummaryPaginationResponse,
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy import select
|
||||
@@ -15,7 +16,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
)
|
||||
@@ -101,15 +101,15 @@ class ConversationVariablesApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||
@model_validate(ConversationVariablesQuery)
|
||||
def get(self, req_data: ConversationVariablesQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
args = ConversationVariablesQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
stmt = (
|
||||
select(ConversationVariable)
|
||||
.where(ConversationVariable.app_id == app_model.id)
|
||||
.order_by(ConversationVariable.created_at)
|
||||
)
|
||||
stmt = stmt.where(ConversationVariable.conversation_id == req_data.conversation_id)
|
||||
stmt = stmt.where(ConversationVariable.conversation_id == args.conversation_id)
|
||||
|
||||
# NOTE: This is a temporary solution to avoid performance issues.
|
||||
page = 1
|
||||
|
||||
@@ -17,12 +17,7 @@ from controllers.console.app.error import (
|
||||
ProviderQuotaExceededError,
|
||||
)
|
||||
from controllers.console.app.wraps import with_session
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
)
|
||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
||||
from core.app.app_config.entities import ModelConfig
|
||||
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||
from core.helper.code_executor.code_node_provider import CodeNodeProvider
|
||||
@@ -243,11 +238,11 @@ class RuleGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleGeneratePayload)
|
||||
def post(self, req_data: RuleGeneratePayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleGeneratePayload.model_validate(console_ns.payload)
|
||||
|
||||
try:
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=req_data)
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=args)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
@@ -272,13 +267,13 @@ class RuleCodeGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleCodeGeneratePayload)
|
||||
def post(self, req_data: RuleCodeGeneratePayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleCodeGeneratePayload.model_validate(console_ns.payload)
|
||||
|
||||
try:
|
||||
code_result = LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
args=req_data,
|
||||
args=args,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -304,13 +299,13 @@ class RuleStructuredOutputGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(RuleStructuredOutputPayload)
|
||||
def post(self, req_data: RuleStructuredOutputPayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleStructuredOutputPayload.model_validate(console_ns.payload)
|
||||
|
||||
try:
|
||||
structured_output = LLMGenerator.generate_structured_output(
|
||||
tenant_id=current_tenant_id,
|
||||
args=req_data,
|
||||
args=args,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -337,36 +332,36 @@ class InstructionGenerateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@with_session(write=False)
|
||||
@model_validate(InstructionGeneratePayload)
|
||||
def post(self, req_data: InstructionGeneratePayload, session: Session, current_tenant_id: str):
|
||||
def post(self, session: Session, current_tenant_id: str):
|
||||
args = InstructionGeneratePayload.model_validate(console_ns.payload)
|
||||
providers: list[type[CodeNodeProvider]] = [Python3CodeProvider, JavascriptCodeProvider]
|
||||
code_provider: type[CodeNodeProvider] | None = next(
|
||||
(p for p in providers if p.is_accept_language(req_data.language)), None
|
||||
(p for p in providers if p.is_accept_language(args.language)), None
|
||||
)
|
||||
code_template = code_provider.get_default_code() if code_provider else ""
|
||||
try:
|
||||
# Generate from nothing for a workflow node
|
||||
if (req_data.current in (code_template, "")) and req_data.node_id != "":
|
||||
if (args.current in (code_template, "")) and args.node_id != "":
|
||||
app = session.scalar(
|
||||
select(App).where(App.id == req_data.flow_id, App.tenant_id == current_tenant_id).limit(1)
|
||||
select(App).where(App.id == args.flow_id, App.tenant_id == current_tenant_id).limit(1)
|
||||
)
|
||||
if not app:
|
||||
return {"error": f"app {req_data.flow_id} not found"}, 400
|
||||
return {"error": f"app {args.flow_id} not found"}, 400
|
||||
workflow = WorkflowService().get_draft_workflow(app_model=app, session=session)
|
||||
if not workflow:
|
||||
return {"error": f"workflow {req_data.flow_id} not found"}, 400
|
||||
return {"error": f"workflow {args.flow_id} not found"}, 400
|
||||
nodes: Sequence = workflow.graph_dict["nodes"]
|
||||
node = [node for node in nodes if node["id"] == req_data.node_id]
|
||||
node = [node for node in nodes if node["id"] == args.node_id]
|
||||
if len(node) == 0:
|
||||
return {"error": f"node {req_data.node_id} not found"}, 400
|
||||
return {"error": f"node {args.node_id} not found"}, 400
|
||||
node_type = node[0]["data"]["type"]
|
||||
match node_type:
|
||||
case "llm":
|
||||
return LLMGenerator.generate_rule_config(
|
||||
current_tenant_id,
|
||||
args=RuleGeneratePayload(
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
@@ -374,8 +369,8 @@ class InstructionGenerateApi(Resource):
|
||||
return LLMGenerator.generate_rule_config(
|
||||
current_tenant_id,
|
||||
args=RuleGeneratePayload(
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
@@ -383,31 +378,31 @@ class InstructionGenerateApi(Resource):
|
||||
return LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
args=RuleCodeGeneratePayload(
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
code_language=req_data.language,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
code_language=args.language,
|
||||
),
|
||||
)
|
||||
case _:
|
||||
return {"error": f"invalid node type: {node_type}"}
|
||||
if req_data.node_id == "" and req_data.current != "": # For legacy app without a workflow
|
||||
if args.node_id == "" and args.current != "": # For legacy app without a workflow
|
||||
return LLMGenerator.instruction_modify_legacy(
|
||||
tenant_id=current_tenant_id,
|
||||
flow_id=req_data.flow_id,
|
||||
current=req_data.current,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
ideal_output=req_data.ideal_output,
|
||||
flow_id=args.flow_id,
|
||||
current=args.current,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
)
|
||||
if req_data.node_id != "" and req_data.current != "": # For workflow node
|
||||
if args.node_id != "" and args.current != "": # For workflow node
|
||||
return LLMGenerator.instruction_modify_workflow(
|
||||
tenant_id=current_tenant_id,
|
||||
flow_id=req_data.flow_id,
|
||||
node_id=req_data.node_id,
|
||||
current=req_data.current,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
ideal_output=req_data.ideal_output,
|
||||
flow_id=args.flow_id,
|
||||
node_id=args.node_id,
|
||||
current=args.current,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
workflow_service=WorkflowService(),
|
||||
)
|
||||
return {"error": "incompatible parameters"}, 400
|
||||
@@ -431,9 +426,9 @@ class InstructionGenerationTemplateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@model_validate(InstructionTemplatePayload)
|
||||
def post(self, req_data: InstructionTemplatePayload):
|
||||
match req_data.type:
|
||||
def post(self):
|
||||
args = InstructionTemplatePayload.model_validate(console_ns.payload)
|
||||
match args.type:
|
||||
case "prompt":
|
||||
from core.llm_generator.prompts import INSTRUCTION_GENERATE_TEMPLATE_PROMPT
|
||||
|
||||
@@ -443,7 +438,7 @@ class InstructionGenerationTemplateApi(Resource):
|
||||
|
||||
return {"data": INSTRUCTION_GENERATE_TEMPLATE_CODE}
|
||||
case _:
|
||||
raise ValueError(f"Invalid type: {req_data.type}")
|
||||
raise ValueError(f"Invalid type: {args.type}")
|
||||
|
||||
|
||||
def _workflow_instruction_guard(args: WorkflowGeneratePayload) -> tuple[dict, int] | None:
|
||||
@@ -497,24 +492,24 @@ class WorkflowGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowGeneratePayload.model_validate(console_ns.payload)
|
||||
|
||||
# Reject empty / over-length instructions at the boundary (shared with
|
||||
# the streaming endpoint) before spending a planner+builder roundtrip.
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
guard = _workflow_instruction_guard(args)
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
try:
|
||||
result = WorkflowGeneratorService.generate_workflow_graph(
|
||||
tenant_id=current_tenant_id,
|
||||
mode=req_data.mode,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
ideal_output=req_data.ideal_output,
|
||||
current_graph=req_data.current_graph.model_dump(by_alias=True, exclude_none=True)
|
||||
if req_data.current_graph
|
||||
mode=args.mode,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
current_graph=args.current_graph.model_dump(by_alias=True, exclude_none=True)
|
||||
if args.current_graph
|
||||
else None,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
@@ -552,13 +547,13 @@ class WorkflowInstructionSuggestionsApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(WorkflowInstructionSuggestionsPayload)
|
||||
def post(self, req_data: WorkflowInstructionSuggestionsPayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowInstructionSuggestionsPayload.model_validate(console_ns.payload)
|
||||
suggestions = LLMGenerator.generate_workflow_instruction_suggestions(
|
||||
tenant_id=current_tenant_id,
|
||||
mode=req_data.mode,
|
||||
language=req_data.language,
|
||||
count=req_data.count,
|
||||
mode=args.mode,
|
||||
language=args.language,
|
||||
count=args.count,
|
||||
)
|
||||
return dump_response(WorkflowInstructionSuggestionsResponse, {"suggestions": suggestions})
|
||||
|
||||
@@ -588,12 +583,12 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowGeneratePayload.model_validate(console_ns.payload)
|
||||
|
||||
# Same boundary guards as the blocking endpoint — return a normal 400
|
||||
# JSON for these BEFORE opening the stream.
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
guard = _workflow_instruction_guard(args)
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
@@ -601,14 +596,12 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
try:
|
||||
for event_name, payload in WorkflowGeneratorService.generate_workflow_graph_stream(
|
||||
tenant_id=current_tenant_id,
|
||||
mode=req_data.mode,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
ideal_output=req_data.ideal_output,
|
||||
mode=args.mode,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
current_graph=(
|
||||
req_data.current_graph.model_dump(by_alias=True, exclude_none=True)
|
||||
if req_data.current_graph
|
||||
else None
|
||||
args.current_graph.model_dump(by_alias=True, exclude_none=True) if args.current_graph else None
|
||||
),
|
||||
):
|
||||
body = {"event": event_name, **payload}
|
||||
|
||||
@@ -28,7 +28,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -321,8 +320,8 @@ class MessageFeedbackExportApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@get_app_model
|
||||
@model_validate(FeedbackExportQuery)
|
||||
def get(self, req_data: FeedbackExportQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
args = FeedbackExportQuery.model_validate(request.args.to_dict())
|
||||
|
||||
# Import the service function
|
||||
from services.feedback_service import FeedbackService
|
||||
@@ -331,12 +330,12 @@ class MessageFeedbackExportApi(Resource):
|
||||
export_data = FeedbackService.export_feedbacks(
|
||||
app_model.id,
|
||||
session=db.session(),
|
||||
from_source=req_data.from_source,
|
||||
rating=req_data.rating,
|
||||
has_comment=req_data.has_comment,
|
||||
start_date=req_data.start_date,
|
||||
end_date=req_data.end_date,
|
||||
format_type=req_data.format,
|
||||
from_source=args.from_source,
|
||||
rating=args.rating,
|
||||
has_comment=args.has_comment,
|
||||
start_date=args.start_date,
|
||||
end_date=args.end_date,
|
||||
format_type=args.format,
|
||||
)
|
||||
return export_data
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
is_admin_or_owner_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -99,8 +98,8 @@ class AppSite(Resource):
|
||||
@with_current_user
|
||||
@with_session
|
||||
@get_app_model
|
||||
@model_validate(AppSiteUpdatePayload)
|
||||
def post(self, req_data: AppSiteUpdatePayload, session: Session, current_user: Account, app_model: App):
|
||||
def post(self, session: Session, current_user: Account, app_model: App):
|
||||
args = AppSiteUpdatePayload.model_validate(console_ns.payload or {})
|
||||
site = session.scalar(select(Site).where(Site.app_id == app_model.id).limit(1))
|
||||
if not site:
|
||||
raise NotFound
|
||||
@@ -124,7 +123,7 @@ class AppSite(Resource):
|
||||
"show_workflow_steps",
|
||||
"use_icon_as_answer_icon",
|
||||
]:
|
||||
value = getattr(req_data, attr_name)
|
||||
value = getattr(args, attr_name)
|
||||
if value is not None:
|
||||
setattr(site, attr_name, value)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from decimal import Decimal
|
||||
|
||||
import sqlalchemy as sa
|
||||
from flask import abort
|
||||
from flask import abort, request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -12,7 +12,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -158,8 +157,8 @@ class DailyMessageStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -178,7 +177,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -218,8 +217,8 @@ class DailyConversationStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -238,7 +237,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -277,8 +276,8 @@ class DailyTerminalsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -297,7 +296,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -337,8 +336,8 @@ class DailyTokenCostStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -358,7 +357,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -400,8 +399,8 @@ class AverageSessionInteractionStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("c.created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -428,7 +427,7 @@ FROM
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -479,8 +478,8 @@ class UserSatisfactionRateStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("m.created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -503,7 +502,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -548,8 +547,8 @@ class AverageResponseTimeStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -568,7 +567,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -608,8 +607,8 @@ class TokensPerSecondStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -631,7 +630,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from dateutil.parser import isoparse
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -13,7 +14,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
)
|
||||
@@ -183,11 +183,11 @@ class WorkflowAppLogApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_LOG_AND_ANNOTATION)
|
||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||
@model_validate(WorkflowAppLogQuery)
|
||||
def get(self, req_data: WorkflowAppLogQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get workflow app logs
|
||||
"""
|
||||
args = WorkflowAppLogQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
# get paginate workflow app logs
|
||||
workflow_app_service = WorkflowAppService()
|
||||
@@ -195,15 +195,15 @@ class WorkflowAppLogApi(Resource):
|
||||
workflow_app_log_pagination = workflow_app_service.get_paginate_workflow_app_logs(
|
||||
session=session,
|
||||
app_model=app_model,
|
||||
keyword=req_data.keyword,
|
||||
status=req_data.status,
|
||||
created_at_before=req_data.created_at__before,
|
||||
created_at_after=req_data.created_at__after,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
detail=req_data.detail,
|
||||
created_by_end_user_session_id=req_data.created_by_end_user_session_id,
|
||||
created_by_account=req_data.created_by_account,
|
||||
keyword=args.keyword,
|
||||
status=args.status,
|
||||
created_at_before=args.created_at__before,
|
||||
created_at_after=args.created_at__after,
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
detail=args.detail,
|
||||
created_by_end_user_session_id=args.created_by_end_user_session_id,
|
||||
created_by_account=args.created_by_account,
|
||||
)
|
||||
|
||||
return WorkflowAppLogPaginationResponse.model_validate(
|
||||
@@ -227,19 +227,19 @@ class WorkflowArchivedLogApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_LOG_AND_ANNOTATION)
|
||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||
@model_validate(WorkflowAppLogQuery)
|
||||
def get(self, req_data: WorkflowAppLogQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get workflow archived logs
|
||||
"""
|
||||
args = WorkflowAppLogQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
workflow_app_service = WorkflowAppService()
|
||||
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
|
||||
workflow_app_log_pagination = workflow_app_service.get_paginate_workflow_archive_logs(
|
||||
session=session,
|
||||
app_model=app_model,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
)
|
||||
|
||||
return WorkflowArchivedLogPaginationResponse.model_validate(
|
||||
|
||||
@@ -10,7 +10,6 @@ from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -240,24 +239,18 @@ class WorkflowCommentListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
@model_validate(WorkflowCommentCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: WorkflowCommentCreatePayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
):
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App):
|
||||
"""Create a new workflow comment."""
|
||||
payload = WorkflowCommentCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
result = WorkflowCommentService.create_comment(
|
||||
tenant_id=current_tenant_id,
|
||||
app_id=app_model.id,
|
||||
created_by=current_user.id,
|
||||
content=req_data.content,
|
||||
position_x=req_data.position_x,
|
||||
position_y=req_data.position_y,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
content=payload.content,
|
||||
position_x=payload.position_x,
|
||||
position_y=payload.position_y,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentCreate, result), 201
|
||||
@@ -296,26 +289,19 @@ class WorkflowCommentDetailApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
@model_validate(WorkflowCommentUpdatePayload)
|
||||
def put(
|
||||
self,
|
||||
req_data: WorkflowCommentUpdatePayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
comment_id: str,
|
||||
):
|
||||
def put(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str):
|
||||
"""Update a workflow comment."""
|
||||
payload = WorkflowCommentUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
result = WorkflowCommentService.update_comment(
|
||||
tenant_id=current_tenant_id,
|
||||
app_id=app_model.id,
|
||||
comment_id=comment_id,
|
||||
user_id=current_user.id,
|
||||
content=req_data.content,
|
||||
position_x=req_data.position_x,
|
||||
position_y=req_data.position_y,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
content=payload.content,
|
||||
position_x=payload.position_x,
|
||||
position_y=payload.position_y,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentUpdate, result)
|
||||
@@ -386,26 +372,20 @@ class WorkflowCommentReplyApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
@model_validate(WorkflowCommentReplyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: WorkflowCommentReplyPayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
comment_id: str,
|
||||
):
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str):
|
||||
"""Add a reply to a workflow comment."""
|
||||
# Validate comment access first
|
||||
WorkflowCommentService.validate_comment_access(
|
||||
comment_id=comment_id, tenant_id=current_tenant_id, app_id=app_model.id
|
||||
)
|
||||
|
||||
payload = WorkflowCommentReplyPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
result = WorkflowCommentService.create_reply(
|
||||
comment_id=comment_id,
|
||||
content=req_data.content,
|
||||
content=payload.content,
|
||||
created_by=current_user.id,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentReplyCreate, result), 201
|
||||
@@ -427,30 +407,23 @@ class WorkflowCommentReplyDetailApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
@model_validate(WorkflowCommentReplyPayload)
|
||||
def put(
|
||||
self,
|
||||
req_data: WorkflowCommentReplyPayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
comment_id: str,
|
||||
reply_id: str,
|
||||
):
|
||||
def put(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str, reply_id: str):
|
||||
"""Update a comment reply."""
|
||||
# Validate comment access first
|
||||
WorkflowCommentService.validate_comment_access(
|
||||
comment_id=comment_id, tenant_id=current_tenant_id, app_id=app_model.id
|
||||
)
|
||||
|
||||
payload = WorkflowCommentReplyPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
reply = WorkflowCommentService.update_reply(
|
||||
tenant_id=current_tenant_id,
|
||||
app_id=app_model.id,
|
||||
comment_id=comment_id,
|
||||
reply_id=reply_id,
|
||||
user_id=current_user.id,
|
||||
content=req_data.content,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
content=payload.content,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentReplyUpdate, reply)
|
||||
|
||||
@@ -4,7 +4,7 @@ from functools import wraps
|
||||
from typing import Any, Concatenate, Self, TypedDict, override
|
||||
from uuid import UUID
|
||||
|
||||
from flask import Response
|
||||
from flask import Response, request
|
||||
from flask_restx import Resource, fields, marshal, marshal_with
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -22,7 +22,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -360,11 +359,11 @@ class WorkflowVariableCollectionApi(Resource):
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_list_without_value_model)
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@model_validate(WorkflowDraftVariableListQuery)
|
||||
def get(self, req_data: WorkflowDraftVariableListQuery, current_user: Account, app_model: App):
|
||||
def get(self, current_user: Account, app_model: App):
|
||||
"""
|
||||
Get draft workflow
|
||||
"""
|
||||
args = WorkflowDraftVariableListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
# fetch draft workflow by app_model
|
||||
workflow_service = WorkflowService()
|
||||
@@ -379,8 +378,8 @@ class WorkflowVariableCollectionApi(Resource):
|
||||
)
|
||||
workflow_vars = draft_var_srv.list_variables_without_values(
|
||||
app_id=app_model.id,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -480,14 +479,7 @@ class VariableApi(Resource):
|
||||
@console_ns.response(404, "Variable not found")
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_model)
|
||||
@model_validate(WorkflowDraftVariableUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowDraftVariableUpdatePayload,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
variable_id: UUID,
|
||||
):
|
||||
def patch(self, current_user: Account, app_model: App, variable_id: UUID):
|
||||
# Request payload for file types:
|
||||
#
|
||||
# Local File:
|
||||
@@ -512,6 +504,7 @@ class VariableApi(Resource):
|
||||
draft_var_srv = WorkflowDraftVariableService(
|
||||
session=db.session(),
|
||||
)
|
||||
args_model = WorkflowDraftVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
variable_id_str = str(variable_id)
|
||||
variable = ensure_variable_access(
|
||||
@@ -521,8 +514,8 @@ class VariableApi(Resource):
|
||||
current_user_id=current_user.id,
|
||||
)
|
||||
|
||||
new_name = req_data.name
|
||||
raw_value = req_data.value
|
||||
new_name = args_model.name
|
||||
raw_value = args_model.value
|
||||
if new_name is None and raw_value is None:
|
||||
return variable
|
||||
|
||||
@@ -667,13 +660,13 @@ class ConversationVariableCollectionApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
||||
@with_current_user
|
||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||
@model_validate(ConversationVariableUpdatePayload)
|
||||
def post(self, req_data: ConversationVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
payload = ConversationVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
conversation_variables_list = [
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in req_data.conversation_variables
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in payload.conversation_variables
|
||||
]
|
||||
conversation_variables = [
|
||||
variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list
|
||||
@@ -762,24 +755,24 @@ class EnvironmentVariableCollectionApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
||||
@with_current_user
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||
@model_validate(EnvironmentVariableUpdatePayload)
|
||||
def post(self, req_data: EnvironmentVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
payload = EnvironmentVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
environment_variables_list = [
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in req_data.environment_variables
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in payload.environment_variables
|
||||
]
|
||||
environment_variables = [
|
||||
variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list
|
||||
]
|
||||
|
||||
if req_data.patch:
|
||||
if payload.patch:
|
||||
workflow_service.patch_draft_workflow_environment_variables(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
environment_variables=environment_variables,
|
||||
deleted_environment_variable_ids=req_data.deleted_environment_variable_ids,
|
||||
deleted_environment_variable_ids=payload.deleted_environment_variable_ids,
|
||||
session=db.session(),
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import UTC, datetime, timedelta
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy import select
|
||||
@@ -16,7 +17,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -160,21 +160,21 @@ class AdvancedChatAppWorkflowRunListApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||
@model_validate(WorkflowRunListQuery)
|
||||
def get(self, req_data: WorkflowRunListQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get advanced chat app workflow run list
|
||||
"""
|
||||
args: WorkflowRunListArgs = {"limit": req_data.limit}
|
||||
if req_data.last_id is not None:
|
||||
args["last_id"] = req_data.last_id
|
||||
if req_data.status is not None:
|
||||
args["status"] = req_data.status
|
||||
args_model = WorkflowRunListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args: WorkflowRunListArgs = {"limit": args_model.limit}
|
||||
if args_model.last_id is not None:
|
||||
args["last_id"] = args_model.last_id
|
||||
if args_model.status is not None:
|
||||
args["status"] = args_model.status
|
||||
|
||||
# Default to DEBUGGING if not specified
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.triggered_from
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
else WorkflowRunTriggeredFrom.DEBUGGING
|
||||
)
|
||||
|
||||
@@ -258,17 +258,17 @@ class AdvancedChatAppWorkflowRunCountApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT])
|
||||
@model_validate(WorkflowRunCountQuery)
|
||||
def get(self, req_data: WorkflowRunCountQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get advanced chat workflow runs count statistics
|
||||
"""
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
args_model = WorkflowRunCountQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args = args_model.model_dump(exclude_none=True)
|
||||
|
||||
# Default to DEBUGGING if not specified
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.triggered_from
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
else WorkflowRunTriggeredFrom.DEBUGGING
|
||||
)
|
||||
|
||||
@@ -299,21 +299,21 @@ class WorkflowRunListApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||
@model_validate(WorkflowRunListQuery)
|
||||
def get(self, req_data: WorkflowRunListQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get workflow run list
|
||||
"""
|
||||
args: WorkflowRunListArgs = {"limit": req_data.limit}
|
||||
if req_data.last_id is not None:
|
||||
args["last_id"] = req_data.last_id
|
||||
if req_data.status is not None:
|
||||
args["status"] = req_data.status
|
||||
args_model = WorkflowRunListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args: WorkflowRunListArgs = {"limit": args_model.limit}
|
||||
if args_model.last_id is not None:
|
||||
args["last_id"] = args_model.last_id
|
||||
if args_model.status is not None:
|
||||
args["status"] = args_model.status
|
||||
|
||||
# Default to DEBUGGING for workflow if not specified (backward compatibility)
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.triggered_from
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
else WorkflowRunTriggeredFrom.DEBUGGING
|
||||
)
|
||||
|
||||
@@ -341,17 +341,17 @@ class WorkflowRunCountApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_CREATE_AND_MANAGEMENT)
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||
@model_validate(WorkflowRunCountQuery)
|
||||
def get(self, req_data: WorkflowRunCountQuery, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""
|
||||
Get workflow runs count statistics
|
||||
"""
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
args_model = WorkflowRunCountQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args = args_model.model_dump(exclude_none=True)
|
||||
|
||||
# Default to DEBUGGING for workflow if not specified (backward compatibility)
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.triggered_from
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
else WorkflowRunTriggeredFrom.DEBUGGING
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import abort, jsonify
|
||||
from flask import abort, jsonify, request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -10,7 +10,6 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -105,13 +104,13 @@ class WorkflowDailyRunsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -149,13 +148,13 @@ class WorkflowDailyTerminalsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -193,13 +192,13 @@ class WorkflowDailyTokenCostStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -237,13 +236,13 @@ class WorkflowAverageAppInteractionStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, field_validator
|
||||
from sqlalchemy import select
|
||||
@@ -24,7 +25,6 @@ from ..wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -102,11 +102,11 @@ class WebhookTriggerApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[WebhookTriggerResponse.__name__])
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@get_app_model(mode=AppMode.WORKFLOW)
|
||||
@model_validate(Parser)
|
||||
def get(self, req_data: Parser, app_model: App):
|
||||
def get(self, app_model: App):
|
||||
"""Get webhook trigger for a node"""
|
||||
args = Parser.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
node_id = req_data.node_id
|
||||
node_id = args.node_id
|
||||
|
||||
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
|
||||
# Get webhook trigger for this app and node
|
||||
@@ -175,11 +175,11 @@ class AppTriggerEnableApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[WorkflowTriggerResponse.__name__])
|
||||
@with_current_tenant_id
|
||||
@get_app_model(mode=AppMode.WORKFLOW)
|
||||
@model_validate(ParserEnable)
|
||||
def post(self, req_data: ParserEnable, current_tenant_id: str, app_model: App):
|
||||
def post(self, current_tenant_id: str, app_model: App):
|
||||
"""Update app trigger (enable/disable)"""
|
||||
args = ParserEnable.model_validate(console_ns.payload)
|
||||
|
||||
trigger_id = req_data.trigger_id
|
||||
trigger_id = args.trigger_id
|
||||
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
|
||||
# Find the trigger using select
|
||||
trigger = session.execute(
|
||||
@@ -194,7 +194,7 @@ class AppTriggerEnableApi(Resource):
|
||||
raise NotFound("Trigger not found")
|
||||
|
||||
# Update status based on enable_trigger boolean
|
||||
trigger.status = AppTriggerStatus.ENABLED if req_data.enable_trigger else AppTriggerStatus.DISABLED
|
||||
trigger.status = AppTriggerStatus.ENABLED if args.enable_trigger else AppTriggerStatus.DISABLED
|
||||
|
||||
# Add computed icon field
|
||||
url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/"
|
||||
|
||||
@@ -36,7 +36,6 @@ from ..wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -230,18 +229,12 @@ class DataSourceNotionListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session(write=False)
|
||||
@model_validate(DataSourceNotionListQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: DataSourceNotionListQuery,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
) -> tuple[dict[str, Any], int]:
|
||||
def get(self, session: Session, current_tenant_id: str, current_user: Account) -> tuple[dict[str, Any], int]:
|
||||
query = DataSourceNotionListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
credential = datasource_provider_service.get_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_id=query.credential_id,
|
||||
provider="notion_datasource",
|
||||
plugin_id="langgenius/notion_datasource",
|
||||
)
|
||||
@@ -249,8 +242,8 @@ class DataSourceNotionListApi(Resource):
|
||||
raise NotFound("Credential not found.")
|
||||
exist_page_ids = []
|
||||
# import notion in the exist dataset
|
||||
if req_data.dataset_id:
|
||||
dataset = DatasetService.get_dataset(req_data.dataset_id, session)
|
||||
if query.dataset_id:
|
||||
dataset = DatasetService.get_dataset(query.dataset_id, session)
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
if dataset.data_source_type != "notion_import":
|
||||
@@ -258,7 +251,7 @@ class DataSourceNotionListApi(Resource):
|
||||
|
||||
documents = session.scalars(
|
||||
select(Document).where(
|
||||
Document.dataset_id == req_data.dataset_id,
|
||||
Document.dataset_id == query.dataset_id,
|
||||
Document.tenant_id == current_tenant_id,
|
||||
Document.data_source_type == "notion_import",
|
||||
Document.enabled.is_(True),
|
||||
@@ -325,19 +318,13 @@ class DataSourceNotionPreviewApi(Resource):
|
||||
@console_ns.doc(params=query_params_from_model(DataSourceNotionPreviewQuery))
|
||||
@console_ns.response(200, "Success", console_ns.models[TextContentResponse.__name__])
|
||||
@with_current_tenant_id
|
||||
@model_validate(DataSourceNotionPreviewQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: DataSourceNotionPreviewQuery,
|
||||
current_tenant_id: str,
|
||||
page_id: UUID,
|
||||
page_type: str,
|
||||
) -> tuple[dict[str, str], int]:
|
||||
def get(self, current_tenant_id: str, page_id: UUID, page_type: str) -> tuple[dict[str, str], int]:
|
||||
query = DataSourceNotionPreviewQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
credential = datasource_provider_service.get_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_id=query.credential_id,
|
||||
provider="notion_datasource",
|
||||
plugin_id="langgenius/notion_datasource",
|
||||
)
|
||||
@@ -367,17 +354,12 @@ class DataSourceNotionIndexingEstimateApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[IndexingEstimate.__name__])
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(NotionEstimatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: NotionEstimatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
) -> tuple[dict[str, Any], int]:
|
||||
args = req_data.model_dump()
|
||||
def post(self, session: Session, current_tenant_id: str) -> tuple[dict[str, Any], int]:
|
||||
payload = NotionEstimatePayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump()
|
||||
# validate args
|
||||
DocumentService.estimate_args_validate(args)
|
||||
notion_info_list = req_data.notion_info_list
|
||||
notion_info_list = payload.notion_info_list
|
||||
extract_settings = []
|
||||
for notion_info in notion_info_list:
|
||||
workspace_id = notion_info["workspace_id"]
|
||||
|
||||
@@ -26,7 +26,6 @@ from controllers.console.wraps import (
|
||||
cloud_edition_billing_rate_limit_check,
|
||||
enterprise_license_required,
|
||||
is_admin_or_owner_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -573,8 +572,9 @@ class DatasetListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(DatasetCreatePayload)
|
||||
def post(self, req_data: DatasetCreatePayload, session: Session, current_tenant_id: str, current_user: Account):
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
payload = DatasetCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||
if not current_user.is_dataset_editor:
|
||||
raise Forbidden()
|
||||
@@ -582,20 +582,20 @@ class DatasetListApi(Resource):
|
||||
if dify_config.RBAC_ENABLED:
|
||||
permission = DatasetPermissionEnum.ALL_TEAM
|
||||
else:
|
||||
permission = req_data.permission or DatasetPermissionEnum.ONLY_ME
|
||||
permission = payload.permission or DatasetPermissionEnum.ONLY_ME
|
||||
|
||||
try:
|
||||
dataset = DatasetService.create_empty_dataset(
|
||||
session=session,
|
||||
tenant_id=current_tenant_id,
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
indexing_technique=req_data.indexing_technique,
|
||||
name=payload.name,
|
||||
description=payload.description,
|
||||
indexing_technique=payload.indexing_technique,
|
||||
account=current_user,
|
||||
permission=permission,
|
||||
provider=req_data.provider,
|
||||
external_knowledge_api_id=req_data.external_knowledge_api_id,
|
||||
external_knowledge_id=req_data.external_knowledge_id,
|
||||
provider=payload.provider,
|
||||
external_knowledge_api_id=payload.external_knowledge_api_id,
|
||||
external_knowledge_id=payload.external_knowledge_id,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
raise DatasetNameDuplicateError()
|
||||
@@ -715,35 +715,28 @@ class DatasetApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(DatasetUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: DatasetUpdatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
dataset_id: UUID,
|
||||
):
|
||||
def patch(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||
dataset_id_str = str(dataset_id)
|
||||
dataset = DatasetService.get_dataset(dataset_id_str, session)
|
||||
if dataset is None:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
payload = DatasetUpdatePayload.model_validate(console_ns.payload or {})
|
||||
# check embedding model setting
|
||||
if (
|
||||
req_data.indexing_technique == IndexTechniqueType.HIGH_QUALITY
|
||||
and req_data.embedding_model_provider is not None
|
||||
and req_data.embedding_model is not None
|
||||
payload.indexing_technique == IndexTechniqueType.HIGH_QUALITY
|
||||
and payload.embedding_model_provider is not None
|
||||
and payload.embedding_model is not None
|
||||
):
|
||||
is_multimodal = DatasetService.check_is_multimodal_model(
|
||||
dataset.tenant_id, req_data.embedding_model_provider, req_data.embedding_model
|
||||
dataset.tenant_id, payload.embedding_model_provider, payload.embedding_model
|
||||
)
|
||||
req_data.is_multimodal = is_multimodal
|
||||
payload_data = req_data.model_dump(exclude_unset=True)
|
||||
payload.is_multimodal = is_multimodal
|
||||
payload_data = payload.model_dump(exclude_unset=True)
|
||||
# The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
DatasetPermissionService.check_permission(
|
||||
current_user, dataset, req_data.permission, req_data.partial_member_list, session=session
|
||||
current_user, dataset, payload.permission, payload.partial_member_list, session=session
|
||||
)
|
||||
|
||||
dataset = DatasetService.update_dataset(dataset_id_str, payload_data, current_user, session=session)
|
||||
@@ -761,12 +754,12 @@ class DatasetApi(Resource):
|
||||
result_data["permission_keys"] = permission_keys_map.get(dataset_id_str, [])
|
||||
tenant_id = current_tenant_id
|
||||
|
||||
if req_data.partial_member_list is not None and req_data.permission == DatasetPermissionEnum.PARTIAL_TEAM:
|
||||
if payload.partial_member_list is not None and payload.permission == DatasetPermissionEnum.PARTIAL_TEAM:
|
||||
DatasetPermissionService.update_partial_member_list(
|
||||
tenant_id, dataset_id_str, req_data.partial_member_list, session
|
||||
tenant_id, dataset_id_str, payload.partial_member_list, session
|
||||
)
|
||||
# clear partial member list when permission is only_me or all_team_members
|
||||
elif req_data.permission in {DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM}:
|
||||
elif payload.permission in {DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM}:
|
||||
DatasetPermissionService.clear_partial_member_list(dataset_id_str, session)
|
||||
|
||||
partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str, session)
|
||||
@@ -879,9 +872,9 @@ class DatasetIndexingEstimateApi(Resource):
|
||||
@console_ns.expect(console_ns.models[IndexingEstimatePayload.__name__])
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(IndexingEstimatePayload)
|
||||
def post(self, req_data: IndexingEstimatePayload, session: Session, current_tenant_id: str):
|
||||
args = req_data.model_dump()
|
||||
def post(self, session: Session, current_tenant_id: str):
|
||||
payload = IndexingEstimatePayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump()
|
||||
# validate args
|
||||
DocumentService.estimate_args_validate(args)
|
||||
extract_settings = []
|
||||
|
||||
@@ -36,7 +36,6 @@ from controllers.console.wraps import (
|
||||
cloud_edition_billing_knowledge_limit_check,
|
||||
cloud_edition_billing_rate_limit_check,
|
||||
cloud_edition_billing_resource_check,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -415,10 +414,8 @@ class DatasetDocumentSegmentAddApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(SegmentCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: SegmentCreatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -458,7 +455,8 @@ class DatasetDocumentSegmentAddApi(Resource):
|
||||
except services.errors.account.NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
# validate args
|
||||
payload_dict = req_data.model_dump(exclude_none=True)
|
||||
payload = SegmentCreatePayload.model_validate(console_ns.payload or {})
|
||||
payload_dict = payload.model_dump(exclude_none=True)
|
||||
SegmentService.segment_create_args_validate(payload_dict, document)
|
||||
segment = type_cast(DocumentSegment, SegmentService.create_segment(payload_dict, document, dataset, session))
|
||||
summary = SummaryIndexService.get_segment_summary(
|
||||
@@ -487,10 +485,8 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(SegmentUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: SegmentUpdatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -536,12 +532,13 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
||||
segment_id_str = str(segment_id)
|
||||
_, segment = _get_segment_for_document(session, dataset, document, segment_id_str)
|
||||
# validate args
|
||||
payload_dict = req_data.model_dump(exclude_none=True)
|
||||
payload = SegmentUpdatePayload.model_validate(console_ns.payload or {})
|
||||
payload_dict = payload.model_dump(exclude_none=True)
|
||||
SegmentService.segment_create_args_validate(payload_dict, document)
|
||||
|
||||
# Update segment (summary update with change detection is handled in SegmentService.update_segment)
|
||||
segment = SegmentService.update_segment(
|
||||
SegmentUpdateArgs.model_validate(req_data.model_dump(exclude_none=True)),
|
||||
SegmentUpdateArgs.model_validate(payload.model_dump(exclude_none=True)),
|
||||
segment,
|
||||
document,
|
||||
dataset,
|
||||
@@ -619,10 +616,8 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(BatchImportPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: BatchImportPayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -640,7 +635,8 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
upload_file_id = req_data.upload_file_id
|
||||
payload = BatchImportPayload.model_validate(console_ns.payload or {})
|
||||
upload_file_id = payload.upload_file_id
|
||||
|
||||
upload_file = session.scalar(select(UploadFile).where(UploadFile.id == upload_file_id).limit(1))
|
||||
if not upload_file:
|
||||
@@ -701,10 +697,8 @@ class ChildChunkAddApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(ChildChunkCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: ChildChunkCreatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -748,7 +742,8 @@ class ChildChunkAddApi(Resource):
|
||||
_, segment = _get_segment_for_document(session, dataset, document, segment_id_str)
|
||||
# validate args
|
||||
try:
|
||||
child_chunk = SegmentService.create_child_chunk(req_data.content, segment, document, dataset, session)
|
||||
payload = ChildChunkCreatePayload.model_validate(console_ns.payload or {})
|
||||
child_chunk = SegmentService.create_child_chunk(payload.content, segment, document, dataset, session)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
return dump_response(ChildChunkDetailResponse, {"data": child_chunk}), 200
|
||||
@@ -817,10 +812,8 @@ class ChildChunkAddApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(ChildChunkBatchUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: ChildChunkBatchUpdatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -850,8 +843,9 @@ class ChildChunkAddApi(Resource):
|
||||
segment_id_str = str(segment_id)
|
||||
_, segment = _get_segment_for_document(session, dataset, document, segment_id_str)
|
||||
# validate args
|
||||
payload = ChildChunkBatchUpdatePayload.model_validate(console_ns.payload or {})
|
||||
try:
|
||||
child_chunks = SegmentService.update_child_chunks(req_data.chunks, segment, document, dataset, session)
|
||||
child_chunks = SegmentService.update_child_chunks(payload.chunks, segment, document, dataset, session)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
return dump_response(ChildChunkBatchUpdateResponse, {"data": child_chunks}), 200
|
||||
@@ -924,10 +918,8 @@ class ChildChunkUpdateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(ChildChunkUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: ChildChunkUpdatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
@@ -963,8 +955,9 @@ class ChildChunkUpdateApi(Resource):
|
||||
raise NotFound("Child chunk not found.")
|
||||
# validate args
|
||||
try:
|
||||
payload = ChildChunkUpdatePayload.model_validate(console_ns.payload or {})
|
||||
child_chunk = SegmentService.update_child_chunk(
|
||||
req_data.content, child_chunk, segment, document, dataset, session
|
||||
payload.content, child_chunk, segment, document, dataset, session
|
||||
)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import AliasChoices, BaseModel, Field, field_validator
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -25,7 +26,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -186,18 +186,18 @@ class ExternalApiTemplateListApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@account_initialization_required
|
||||
@with_session(write=False)
|
||||
@model_validate(ExternalApiTemplateListQuery)
|
||||
def get(self, req_data: ExternalApiTemplateListQuery, session: Session, current_tenant_id: str):
|
||||
def get(self, session: Session, current_tenant_id: str):
|
||||
query = ExternalApiTemplateListQuery.model_validate(request.args.to_dict())
|
||||
|
||||
external_knowledge_apis, total = ExternalDatasetService.get_external_knowledge_apis(
|
||||
req_data.page, req_data.limit, current_tenant_id, req_data.keyword, session=session
|
||||
query.page, query.limit, current_tenant_id, query.keyword, session=session
|
||||
)
|
||||
return ExternalKnowledgeApiListResponse(
|
||||
data=[external_knowledge_api_response(item, session=session) for item in external_knowledge_apis],
|
||||
has_more=len(external_knowledge_apis) == req_data.limit,
|
||||
limit=req_data.limit,
|
||||
has_more=len(external_knowledge_apis) == query.limit,
|
||||
limit=query.limit,
|
||||
total=total,
|
||||
page=req_data.page,
|
||||
page=query.page,
|
||||
).model_dump(mode="json"), 200
|
||||
|
||||
@console_ns.doc("create_external_api_template")
|
||||
@@ -215,16 +215,10 @@ class ExternalApiTemplateListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(ExternalKnowledgeApiPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: ExternalKnowledgeApiPayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
):
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
payload = ExternalKnowledgeApiPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
ExternalDatasetService.validate_api_list(req_data.settings)
|
||||
ExternalDatasetService.validate_api_list(payload.settings)
|
||||
|
||||
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||
if not current_user.is_dataset_editor:
|
||||
@@ -234,7 +228,7 @@ class ExternalApiTemplateListApi(Resource):
|
||||
external_knowledge_api = ExternalDatasetService.create_external_knowledge_api(
|
||||
tenant_id=current_tenant_id,
|
||||
user_id=current_user.id,
|
||||
args=req_data.model_dump(),
|
||||
args=payload.model_dump(),
|
||||
session=session,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
@@ -285,24 +279,17 @@ class ExternalApiTemplateApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(ExternalKnowledgeApiPayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: ExternalKnowledgeApiPayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
external_knowledge_api_id: UUID,
|
||||
):
|
||||
def patch(self, session: Session, current_tenant_id: str, current_user: Account, external_knowledge_api_id: UUID):
|
||||
external_knowledge_api_id_str = str(external_knowledge_api_id)
|
||||
|
||||
ExternalDatasetService.validate_api_list(req_data.settings)
|
||||
payload = ExternalKnowledgeApiPayload.model_validate(console_ns.payload or {})
|
||||
ExternalDatasetService.validate_api_list(payload.settings)
|
||||
|
||||
external_knowledge_api = ExternalDatasetService.update_external_knowledge_api(
|
||||
tenant_id=current_tenant_id,
|
||||
user_id=current_user.id,
|
||||
external_knowledge_api_id=external_knowledge_api_id_str,
|
||||
args=req_data.model_dump(),
|
||||
args=payload.model_dump(),
|
||||
session=session,
|
||||
)
|
||||
|
||||
@@ -367,15 +354,9 @@ class ExternalDatasetCreateApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(ExternalDatasetCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: ExternalDatasetCreatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
):
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
# The role of the current user in the ta table must be admin, owner, or editor
|
||||
payload = ExternalDatasetCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||
if not current_user.is_dataset_editor:
|
||||
@@ -385,7 +366,7 @@ class ExternalDatasetCreateApi(Resource):
|
||||
dataset = ExternalDatasetService.create_external_dataset(
|
||||
tenant_id=current_tenant_id,
|
||||
user_id=current_user.id,
|
||||
args=req_data,
|
||||
args=payload,
|
||||
session=session,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
@@ -424,8 +405,7 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_PIPELINE_TEST)
|
||||
@with_session
|
||||
@model_validate(ExternalHitTestingPayload)
|
||||
def post(self, req_data: ExternalHitTestingPayload, session: Session, current_user: Account, dataset_id: UUID):
|
||||
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||
dataset_id_str = str(dataset_id)
|
||||
dataset = DatasetService.get_dataset(dataset_id_str, session)
|
||||
if dataset is None:
|
||||
@@ -436,16 +416,17 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
||||
except services.errors.account.NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
|
||||
HitTestingService.hit_testing_args_check(req_data.model_dump())
|
||||
payload = ExternalHitTestingPayload.model_validate(console_ns.payload or {})
|
||||
HitTestingService.hit_testing_args_check(payload.model_dump())
|
||||
|
||||
try:
|
||||
response = HitTestingService.external_retrieve(
|
||||
session=session,
|
||||
dataset=dataset,
|
||||
query=req_data.query,
|
||||
query=payload.query,
|
||||
account=current_user,
|
||||
external_retrieval_model=req_data.external_retrieval_model,
|
||||
metadata_filtering_conditions=req_data.metadata_filtering_conditions,
|
||||
external_retrieval_model=payload.external_retrieval_model,
|
||||
metadata_filtering_conditions=payload.metadata_filtering_conditions,
|
||||
)
|
||||
|
||||
return dump_response(ExternalHitTestingResponse, response)
|
||||
@@ -460,11 +441,11 @@ class BedrockRetrievalApi(Resource):
|
||||
@console_ns.doc(description="Bedrock retrieval test (internal use only)")
|
||||
@console_ns.expect(console_ns.models[BedrockRetrievalPayload.__name__])
|
||||
@console_ns.response(200, "Bedrock retrieval test completed", console_ns.models[BedrockRetrievalResponse.__name__])
|
||||
@model_validate(BedrockRetrievalPayload)
|
||||
def post(self, req_data: BedrockRetrievalPayload):
|
||||
def post(self):
|
||||
payload = BedrockRetrievalPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
# Call the knowledge retrieval service
|
||||
result = ExternalDatasetTestService.knowledge_retrieval(
|
||||
req_data.retrieval_setting, req_data.query, req_data.knowledge_id
|
||||
payload.retrieval_setting, payload.query, payload.knowledge_id
|
||||
)
|
||||
return dump_response(BedrockRetrievalResponse, result), 200
|
||||
|
||||
@@ -14,7 +14,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
enterprise_license_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -60,15 +59,9 @@ class DatasetMetadataCreateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(MetadataArgs)
|
||||
def post(
|
||||
self,
|
||||
req_data: MetadataArgs,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
dataset_id: UUID,
|
||||
):
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||
metadata_args = MetadataArgs.model_validate(console_ns.payload or {})
|
||||
|
||||
dataset_id_str = str(dataset_id)
|
||||
dataset = DatasetService.get_dataset(dataset_id_str, session)
|
||||
if dataset is None:
|
||||
@@ -76,7 +69,7 @@ class DatasetMetadataCreateApi(Resource):
|
||||
DatasetService.check_dataset_permission(dataset, current_user, session)
|
||||
|
||||
metadata = MetadataService.create_metadata(
|
||||
dataset_id_str, req_data, current_user, current_tenant_id, session=session
|
||||
dataset_id_str, metadata_args, current_user, current_tenant_id, session=session
|
||||
)
|
||||
return dump_response(DatasetMetadataResponse, metadata), 201
|
||||
|
||||
@@ -110,17 +103,16 @@ class DatasetMetadataApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(MetadataUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: MetadataUpdatePayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
dataset_id: UUID,
|
||||
metadata_id: UUID,
|
||||
):
|
||||
name = req_data.name
|
||||
payload = MetadataUpdatePayload.model_validate(console_ns.payload or {})
|
||||
name = payload.name
|
||||
|
||||
dataset_id_str = str(dataset_id)
|
||||
metadata_id_str = str(metadata_id)
|
||||
@@ -211,15 +203,16 @@ class DocumentMetadataEditApi(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
@model_validate(MetadataOperationData)
|
||||
def post(self, req_data: MetadataOperationData, session: Session, current_user: Account, dataset_id: UUID):
|
||||
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||
dataset_id_str = str(dataset_id)
|
||||
dataset = DatasetService.get_dataset(dataset_id_str, session)
|
||||
if dataset is None:
|
||||
raise NotFound("Dataset not found.")
|
||||
DatasetService.check_dataset_permission(dataset, current_user, session)
|
||||
|
||||
MetadataService.update_documents_metadata(dataset, req_data, current_user, session=session)
|
||||
metadata_args = MetadataOperationData.model_validate(console_ns.payload or {})
|
||||
|
||||
MetadataService.update_documents_metadata(dataset, metadata_args, current_user, session=session)
|
||||
|
||||
# Frontend callers only await success and invalidate caches; no response body is consumed.
|
||||
return "", 204
|
||||
|
||||
@@ -14,7 +14,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -276,8 +275,8 @@ class DatasourceAuth(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceCredentialPayload)
|
||||
def post(self, req_data: DatasourceCredentialPayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceCredentialPayload.model_validate(console_ns.payload or {})
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
|
||||
@@ -285,8 +284,8 @@ class DatasourceAuth(Resource):
|
||||
datasource_provider_service.add_datasource_api_key_provider(
|
||||
tenant_id=current_tenant_id,
|
||||
provider_id=datasource_provider_id,
|
||||
credentials=req_data.credentials,
|
||||
name=req_data.name,
|
||||
credentials=payload.credentials,
|
||||
name=payload.name,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
raise ValueError(str(ex))
|
||||
@@ -326,16 +325,16 @@ class DatasourceAuthDeleteApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceCredentialDeletePayload)
|
||||
def post(self, req_data: DatasourceCredentialDeletePayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
plugin_id = datasource_provider_id.plugin_id
|
||||
provider_name = datasource_provider_id.provider_name
|
||||
|
||||
payload = DatasourceCredentialDeletePayload.model_validate(console_ns.payload or {})
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
datasource_provider_service.remove_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
auth_id=req_data.credential_id,
|
||||
auth_id=payload.credential_id,
|
||||
provider=provider_name,
|
||||
plugin_id=plugin_id,
|
||||
session=db.session(),
|
||||
@@ -355,18 +354,18 @@ class DatasourceAuthUpdateApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceCredentialUpdatePayload)
|
||||
def post(self, req_data: DatasourceCredentialUpdatePayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
payload = DatasourceCredentialUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
datasource_provider_service.update_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
auth_id=req_data.credential_id,
|
||||
auth_id=payload.credential_id,
|
||||
provider=datasource_provider_id.provider_name,
|
||||
plugin_id=datasource_provider_id.plugin_id,
|
||||
credentials=req_data.credentials or {},
|
||||
name=req_data.name,
|
||||
credentials=payload.credentials or {},
|
||||
name=payload.name,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 201
|
||||
|
||||
@@ -421,15 +420,15 @@ class DatasourceAuthOauthCustomClient(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceCustomClientPayload)
|
||||
def post(self, req_data: DatasourceCustomClientPayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceCustomClientPayload.model_validate(console_ns.payload or {})
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
datasource_provider_service.setup_oauth_custom_client_params(
|
||||
tenant_id=current_tenant_id,
|
||||
datasource_provider_id=datasource_provider_id,
|
||||
client_params=req_data.client_params or {},
|
||||
enabled=req_data.enable_oauth_custom_client or False,
|
||||
client_params=payload.client_params or {},
|
||||
enabled=payload.enable_oauth_custom_client or False,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
|
||||
@@ -458,14 +457,14 @@ class DatasourceAuthDefaultApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceDefaultPayload)
|
||||
def post(self, req_data: DatasourceDefaultPayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceDefaultPayload.model_validate(console_ns.payload or {})
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
datasource_provider_service.set_default_datasource_provider(
|
||||
tenant_id=current_tenant_id,
|
||||
datasource_provider_id=datasource_provider_id,
|
||||
credential_id=req_data.id,
|
||||
credential_id=payload.id,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
|
||||
@@ -480,14 +479,14 @@ class DatasourceUpdateProviderNameApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
@model_validate(DatasourceUpdateNamePayload)
|
||||
def post(self, req_data: DatasourceUpdateNamePayload, current_tenant_id: str, provider_id: str):
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceUpdateNamePayload.model_validate(console_ns.payload or {})
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
datasource_provider_service.update_datasource_provider_name(
|
||||
tenant_id=current_tenant_id,
|
||||
datasource_provider_id=datasource_provider_id,
|
||||
name=req_data.name,
|
||||
credential_id=req_data.credential_id,
|
||||
name=payload.name,
|
||||
credential_id=payload.credential_id,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic import BaseModel
|
||||
from controllers.common.schema import register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.datasets.wraps import get_rag_pipeline
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, setup_required, with_current_user
|
||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_user
|
||||
from extensions.ext_database import db
|
||||
from libs.login import login_required
|
||||
from models import Account
|
||||
@@ -34,14 +34,14 @@ class DataSourceContentPreviewApi(Resource):
|
||||
@account_initialization_required
|
||||
@get_rag_pipeline
|
||||
@with_current_user
|
||||
@model_validate(Parser)
|
||||
def post(self, req_data: Parser, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run datasource content preview
|
||||
"""
|
||||
args = Parser.model_validate(console_ns.payload)
|
||||
|
||||
inputs = req_data.inputs
|
||||
datasource_type = req_data.datasource_type
|
||||
inputs = args.inputs
|
||||
datasource_type = args.datasource_type
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
preview_content = rag_pipeline_service.run_datasource_node_preview(
|
||||
pipeline=pipeline,
|
||||
@@ -50,6 +50,6 @@ class DataSourceContentPreviewApi(Resource):
|
||||
account=current_user,
|
||||
datasource_type=datasource_type,
|
||||
is_published=True,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_id=args.credential_id,
|
||||
)
|
||||
return preview_content, 200
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
@@ -20,7 +21,6 @@ from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
enterprise_license_required,
|
||||
knowledge_pipeline_publish_enabled,
|
||||
model_validate,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -104,17 +104,12 @@ class PipelineTemplateListApi(Resource):
|
||||
@enterprise_license_required
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
@model_validate(PipelineTemplateListQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: PipelineTemplateListQuery,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
) -> JsonResponseWithStatus:
|
||||
def get(self, session: Session, current_tenant_id: str) -> JsonResponseWithStatus:
|
||||
query = PipelineTemplateListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
# get pipeline templates
|
||||
pipeline_templates = RagPipelineService.get_pipeline_templates(
|
||||
type=req_data.type,
|
||||
language=req_data.language,
|
||||
type=query.type,
|
||||
language=query.language,
|
||||
current_tenant_id=current_tenant_id,
|
||||
session=session,
|
||||
)
|
||||
@@ -130,11 +125,11 @@ class PipelineTemplateDetailApi(Resource):
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@with_session
|
||||
@model_validate(PipelineTemplateDetailQuery)
|
||||
def get(self, req_data: PipelineTemplateDetailQuery, session: Session, template_id: str) -> JsonResponseWithStatus:
|
||||
def get(self, session: Session, template_id: str) -> JsonResponseWithStatus:
|
||||
query = PipelineTemplateDetailQuery.model_validate(request.args.to_dict(flat=True))
|
||||
pipeline_template = RagPipelineService.get_pipeline_template_detail(
|
||||
template_id,
|
||||
type=req_data.type,
|
||||
type=query.type,
|
||||
session=session,
|
||||
)
|
||||
if pipeline_template is None:
|
||||
@@ -152,15 +147,9 @@ class CustomizedPipelineTemplateApi(Resource):
|
||||
@enterprise_license_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(CustomizedPipelineTemplatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: CustomizedPipelineTemplatePayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
template_id: str,
|
||||
) -> tuple[str, int]:
|
||||
pipeline_template_info = PipelineTemplateInfoEntity.model_validate(req_data.model_dump())
|
||||
def patch(self, current_tenant_id: str, current_user: Account, template_id: str) -> tuple[str, int]:
|
||||
payload = CustomizedPipelineTemplatePayload.model_validate(console_ns.payload or {})
|
||||
pipeline_template_info = PipelineTemplateInfoEntity.model_validate(payload.model_dump())
|
||||
RagPipelineService.update_customized_pipeline_template(
|
||||
template_id, pipeline_template_info, current_user, current_tenant_id, session=db.session()
|
||||
)
|
||||
@@ -203,16 +192,10 @@ class PublishCustomizedPipelineTemplateApi(Resource):
|
||||
@knowledge_pipeline_publish_enabled
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(CustomizedPipelineTemplatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: CustomizedPipelineTemplatePayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
pipeline_id: str,
|
||||
) -> tuple[str, int]:
|
||||
def post(self, current_tenant_id: str, current_user: Account, pipeline_id: str) -> tuple[str, int]:
|
||||
payload = CustomizedPipelineTemplatePayload.model_validate(console_ns.payload or {})
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
rag_pipeline_service.publish_customized_pipeline_template(
|
||||
pipeline_id, req_data.model_dump(), current_user, current_tenant_id, session=db.session()
|
||||
pipeline_id, payload.model_dump(), current_user, current_tenant_id, session=db.session()
|
||||
)
|
||||
return "", 204
|
||||
|
||||
@@ -10,7 +10,6 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_import import RagPip
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
cloud_edition_billing_rate_limit_check,
|
||||
model_validate,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
@@ -48,13 +47,8 @@ class CreateRagPipelineDatasetApi(Resource):
|
||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(RagPipelineDatasetImportPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: RagPipelineDatasetImportPayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
) -> JsonResponseWithStatus:
|
||||
def post(self, current_tenant_id: str, current_user: Account) -> JsonResponseWithStatus:
|
||||
payload = RagPipelineDatasetImportPayload.model_validate(console_ns.payload or {})
|
||||
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||
if not current_user.is_dataset_editor:
|
||||
raise Forbidden()
|
||||
@@ -68,7 +62,7 @@ class CreateRagPipelineDatasetApi(Resource):
|
||||
),
|
||||
permission=DatasetPermissionEnum.ONLY_ME,
|
||||
partial_member_list=None,
|
||||
yaml_content=req_data.yaml_content,
|
||||
yaml_content=payload.yaml_content,
|
||||
)
|
||||
try:
|
||||
rag_pipeline_dsl_service = RagPipelineDslService(db.session())
|
||||
|
||||
@@ -4,7 +4,7 @@ from functools import wraps
|
||||
from typing import Any, Concatenate, NoReturn
|
||||
from uuid import UUID
|
||||
|
||||
from flask import Response
|
||||
from flask import Response, request
|
||||
from flask_restx import Resource, marshal, marshal_with
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -24,7 +24,7 @@ from controllers.console.app.workflow_draft_variable import (
|
||||
workflow_draft_variable_model,
|
||||
)
|
||||
from controllers.console.datasets.wraps import get_rag_pipeline
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, setup_required, with_current_user
|
||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_user
|
||||
from core.app.file_access import DatabaseFileAccessController
|
||||
from core.workflow.llm_environment_variable import LLMEnvironmentVariable, environment_variable_value_type
|
||||
from core.workflow.variable_prefixes import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
|
||||
@@ -92,11 +92,11 @@ class RagPipelineVariableCollectionApi(Resource):
|
||||
)
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_list_without_value_model)
|
||||
@model_validate(PaginationQuery)
|
||||
def get(self, req_data: PaginationQuery, current_user: Account, pipeline: Pipeline):
|
||||
def get(self, current_user: Account, pipeline: Pipeline):
|
||||
"""
|
||||
Get draft workflow
|
||||
"""
|
||||
query = PaginationQuery.model_validate(request.args.to_dict())
|
||||
|
||||
# fetch draft workflow by app_model
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
@@ -111,8 +111,8 @@ class RagPipelineVariableCollectionApi(Resource):
|
||||
)
|
||||
workflow_vars = draft_var_srv.list_variables_without_values(
|
||||
app_id=pipeline.id,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
page=query.page,
|
||||
limit=query.limit,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -196,14 +196,7 @@ class RagPipelineVariableApi(Resource):
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_model)
|
||||
@console_ns.expect(console_ns.models[WorkflowDraftVariablePatchPayload.__name__])
|
||||
@model_validate(WorkflowDraftVariablePatchPayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowDraftVariablePatchPayload,
|
||||
_current_user: Account,
|
||||
pipeline: Pipeline,
|
||||
variable_id: UUID,
|
||||
):
|
||||
def patch(self, _current_user: Account, pipeline: Pipeline, variable_id: UUID):
|
||||
# Request payload for file types:
|
||||
#
|
||||
# Local File:
|
||||
@@ -228,7 +221,8 @@ class RagPipelineVariableApi(Resource):
|
||||
draft_var_srv = WorkflowDraftVariableService(
|
||||
session=db.session(),
|
||||
)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
payload = WorkflowDraftVariablePatchPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
|
||||
variable_id_str = str(variable_id)
|
||||
variable = draft_var_srv.get_variable(variable_id=variable_id_str)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -16,7 +17,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -85,9 +85,9 @@ class RagPipelineImportApi(Resource):
|
||||
RBACResourceScope.DATASET, RBACPermission.DATASET_CREATE_AND_MANAGEMENT, resource_required=False
|
||||
)
|
||||
@with_current_user
|
||||
@model_validate(RagPipelineImportPayload)
|
||||
def post(self, req_data: RagPipelineImportPayload, current_user: Account) -> JsonResponseWithStatus:
|
||||
def post(self, current_user: Account) -> JsonResponseWithStatus:
|
||||
# Check user role first
|
||||
payload = RagPipelineImportPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
# Use a plain Session so that caught exceptions inside the service
|
||||
# (which return FAILED status instead of re-raising) do not leave the
|
||||
@@ -98,11 +98,11 @@ class RagPipelineImportApi(Resource):
|
||||
account = current_user
|
||||
result = import_service.import_rag_pipeline(
|
||||
account=account,
|
||||
import_mode=req_data.mode,
|
||||
yaml_content=req_data.yaml_content,
|
||||
yaml_url=req_data.yaml_url,
|
||||
pipeline_id=req_data.pipeline_id,
|
||||
dataset_name=req_data.name,
|
||||
import_mode=payload.mode,
|
||||
yaml_content=payload.yaml_content,
|
||||
yaml_url=payload.yaml_url,
|
||||
pipeline_id=payload.pipeline_id,
|
||||
dataset_name=payload.name,
|
||||
)
|
||||
if result.status == ImportStatus.FAILED:
|
||||
session.rollback()
|
||||
@@ -179,14 +179,14 @@ class RagPipelineExportApi(Resource):
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_IMPORT_EXPORT_DSL)
|
||||
@model_validate(IncludeSecretQuery)
|
||||
def get(self, req_data: IncludeSecretQuery, pipeline: Pipeline) -> JsonResponseWithStatus:
|
||||
def get(self, pipeline: Pipeline) -> JsonResponseWithStatus:
|
||||
# Add include_secret params
|
||||
query = IncludeSecretQuery.model_validate(request.args.to_dict())
|
||||
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
export_service = RagPipelineDslService(session)
|
||||
result = export_service.export_rag_pipeline_dsl(
|
||||
pipeline=pipeline, include_secret=req_data.include_secret == "true"
|
||||
pipeline=pipeline, include_secret=query.include_secret == "true"
|
||||
)
|
||||
|
||||
return dump_response(SimpleDataResponse, {"data": result}), 200
|
||||
|
||||
@@ -33,7 +33,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -275,12 +274,12 @@ class RagPipelineDraftRunIterationNodeApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(NodeRunPayload)
|
||||
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow iteration node
|
||||
"""
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
payload = NodeRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate_single_iteration(
|
||||
@@ -310,12 +309,12 @@ class RagPipelineDraftRunLoopNodeApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@model_validate(NodeRunPayload)
|
||||
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow loop node
|
||||
"""
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
payload = NodeRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate_single_loop(
|
||||
@@ -345,13 +344,13 @@ class DraftRagPipelineRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@with_session
|
||||
@model_validate(DraftWorkflowRunPayload)
|
||||
def post(self, req_data: DraftWorkflowRunPayload, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
def post(self, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
"""
|
||||
Run draft workflow
|
||||
"""
|
||||
pipeline = load_rag_pipeline(session, str(pipeline_id))
|
||||
args = req_data.model_dump()
|
||||
payload = DraftWorkflowRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump()
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate(
|
||||
@@ -379,14 +378,14 @@ class PublishedRagPipelineRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@with_session
|
||||
@model_validate(PublishedWorkflowRunPayload)
|
||||
def post(self, req_data: PublishedWorkflowRunPayload, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
def post(self, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
"""
|
||||
Run published workflow
|
||||
"""
|
||||
pipeline = load_rag_pipeline(session, str(pipeline_id))
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
streaming = req_data.response_mode == "streaming"
|
||||
payload = PublishedWorkflowRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
streaming = payload.response_mode == "streaming"
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate(
|
||||
@@ -394,7 +393,7 @@ class PublishedRagPipelineRunApi(Resource):
|
||||
pipeline=pipeline,
|
||||
user=current_user,
|
||||
args=args,
|
||||
invoke_from=InvokeFrom.DEBUGGER if req_data.is_preview else InvokeFrom.PUBLISHED_PIPELINE,
|
||||
invoke_from=InvokeFrom.DEBUGGER if payload.is_preview else InvokeFrom.PUBLISHED_PIPELINE,
|
||||
streaming=streaming,
|
||||
)
|
||||
|
||||
@@ -414,11 +413,11 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@model_validate(DatasourceNodeRunPayload)
|
||||
def post(self, req_data: DatasourceNodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run rag pipeline datasource
|
||||
"""
|
||||
payload = DatasourceNodeRunPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
return helper.compact_generate_response(
|
||||
@@ -426,11 +425,11 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource):
|
||||
rag_pipeline_service.run_datasource_workflow_node(
|
||||
pipeline=pipeline,
|
||||
node_id=node_id,
|
||||
user_inputs=req_data.inputs,
|
||||
user_inputs=payload.inputs,
|
||||
account=current_user,
|
||||
datasource_type=req_data.datasource_type,
|
||||
datasource_type=payload.datasource_type,
|
||||
is_published=False,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_id=payload.credential_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -447,11 +446,11 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@model_validate(DatasourceNodeRunPayload)
|
||||
def post(self, req_data: DatasourceNodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run rag pipeline datasource
|
||||
"""
|
||||
payload = DatasourceNodeRunPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
return helper.compact_generate_response(
|
||||
@@ -459,11 +458,11 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource):
|
||||
rag_pipeline_service.run_datasource_workflow_node(
|
||||
pipeline=pipeline,
|
||||
node_id=node_id,
|
||||
user_inputs=req_data.inputs,
|
||||
user_inputs=payload.inputs,
|
||||
account=current_user,
|
||||
datasource_type=req_data.datasource_type,
|
||||
datasource_type=payload.datasource_type,
|
||||
is_published=False,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_id=payload.credential_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -484,12 +483,12 @@ class RagPipelineDraftNodeRunApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@model_validate(NodeRunRequiredPayload)
|
||||
def post(self, req_data: NodeRunRequiredPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow node
|
||||
"""
|
||||
inputs = req_data.inputs
|
||||
payload = NodeRunRequiredPayload.model_validate(console_ns.payload or {})
|
||||
inputs = payload.inputs
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
workflow_node_execution = rag_pipeline_service.run_draft_workflow_node(
|
||||
@@ -618,16 +617,16 @@ class DefaultRagPipelineBlockConfigApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@get_rag_pipeline
|
||||
@model_validate(DefaultBlockConfigQuery)
|
||||
def get(self, req_data: DefaultBlockConfigQuery, pipeline: Pipeline, block_type: str):
|
||||
def get(self, pipeline: Pipeline, block_type: str):
|
||||
"""
|
||||
Get default block config
|
||||
"""
|
||||
query = DefaultBlockConfigQuery.model_validate(request.args.to_dict())
|
||||
|
||||
filters = None
|
||||
if req_data.q:
|
||||
if query.q:
|
||||
try:
|
||||
filters = json.loads(req_data.q)
|
||||
filters = json.loads(query.q)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("Invalid filters")
|
||||
|
||||
@@ -652,16 +651,16 @@ class PublishedAllRagPipelineApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@model_validate(WorkflowListQuery)
|
||||
def get(self, req_data: WorkflowListQuery, current_user: Account, pipeline: Pipeline):
|
||||
def get(self, current_user: Account, pipeline: Pipeline):
|
||||
"""
|
||||
Get published workflows
|
||||
"""
|
||||
query = WorkflowListQuery.model_validate(request.args.to_dict())
|
||||
|
||||
page = req_data.page
|
||||
limit = req_data.limit
|
||||
user_id = req_data.user_id
|
||||
named_only = req_data.named_only
|
||||
page = query.page
|
||||
limit = query.limit
|
||||
user_id = query.user_id
|
||||
named_only = query.named_only
|
||||
|
||||
if user_id:
|
||||
if user_id != current_user.id:
|
||||
@@ -734,12 +733,12 @@ class RagPipelineByIdApi(Resource):
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@console_ns.expect(console_ns.models[WorkflowUpdatePayload.__name__])
|
||||
@model_validate(WorkflowUpdatePayload)
|
||||
def patch(self, req_data: WorkflowUpdatePayload, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||
def patch(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||
"""
|
||||
Update workflow attributes
|
||||
"""
|
||||
update_data = req_data.model_dump(exclude_unset=True)
|
||||
payload = WorkflowUpdatePayload.model_validate(console_ns.payload or {})
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
|
||||
if not update_data:
|
||||
return {"message": "No valid fields to update"}, 400
|
||||
@@ -804,12 +803,12 @@ class PublishedRagPipelineSecondStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
def get(self, pipeline: Pipeline):
|
||||
"""
|
||||
Get second step parameters of rag pipeline
|
||||
"""
|
||||
node_id = req_data.node_id
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
variables = rag_pipeline_service.get_second_step_parameters(pipeline=pipeline, node_id=node_id, is_draft=False)
|
||||
return {
|
||||
@@ -827,12 +826,12 @@ class PublishedRagPipelineFirstStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
def get(self, pipeline: Pipeline):
|
||||
"""
|
||||
Get first step parameters of rag pipeline
|
||||
"""
|
||||
node_id = req_data.node_id
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
variables = rag_pipeline_service.get_first_step_parameters(pipeline=pipeline, node_id=node_id, is_draft=False)
|
||||
return {
|
||||
@@ -850,12 +849,12 @@ class DraftRagPipelineFirstStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
def get(self, pipeline: Pipeline):
|
||||
"""
|
||||
Get first step parameters of rag pipeline
|
||||
"""
|
||||
node_id = req_data.node_id
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
variables = rag_pipeline_service.get_first_step_parameters(pipeline=pipeline, node_id=node_id, is_draft=True)
|
||||
return {
|
||||
@@ -873,12 +872,12 @@ class DraftRagPipelineSecondStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
def get(self, pipeline: Pipeline):
|
||||
"""
|
||||
Get second step parameters of rag pipeline
|
||||
"""
|
||||
node_id = req_data.node_id
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
variables = rag_pipeline_service.get_second_step_parameters(pipeline=pipeline, node_id=node_id, is_draft=True)
|
||||
@@ -1046,12 +1045,11 @@ class RagPipelineDatasourceVariableApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@model_validate(DatasourceVariablesPayload)
|
||||
def post(self, req_data: DatasourceVariablesPayload, current_user: Account, pipeline: Pipeline):
|
||||
def post(self, current_user: Account, pipeline: Pipeline):
|
||||
"""
|
||||
Set datasource variables
|
||||
"""
|
||||
args = req_data.model_dump()
|
||||
args = DatasourceVariablesPayload.model_validate(console_ns.payload or {}).model_dump()
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
workflow_node_execution = rag_pipeline_service.set_datasource_variables(
|
||||
@@ -1073,11 +1071,9 @@ class RagPipelineRecommendedPluginApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(RagPipelineRecommendedPluginQuery)
|
||||
def get(self, req_data: RagPipelineRecommendedPluginQuery, current_tenant_id: str, current_user: Account):
|
||||
def get(self, current_tenant_id: str, current_user: Account):
|
||||
query = RagPipelineRecommendedPluginQuery.model_validate(request.args.to_dict())
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(
|
||||
req_data.type, current_user, current_tenant_id
|
||||
)
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(query.type, current_user, current_tenant_id)
|
||||
return recommended_plugins
|
||||
|
||||
@@ -41,7 +41,6 @@ from controllers.console.wraps import (
|
||||
cloud_edition_billing_enabled,
|
||||
enable_change_email,
|
||||
enterprise_license_required,
|
||||
model_validate,
|
||||
only_edition_cloud,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -333,9 +332,9 @@ class AccountAvatarApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@model_validate(AccountAvatarQuery)
|
||||
def get(self, req_data: AccountAvatarQuery, current_user: Account):
|
||||
avatar = req_data.avatar
|
||||
def get(self, current_user: Account):
|
||||
args = AccountAvatarQuery.model_validate(request.args.to_dict(flat=True))
|
||||
avatar = args.avatar
|
||||
|
||||
if avatar.startswith(("http://", "https://")):
|
||||
return AvatarUrlResponse(avatar_url=avatar).model_dump(mode="json")
|
||||
|
||||
@@ -11,6 +11,7 @@ from enum import StrEnum
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -293,14 +294,14 @@ class EndpointListApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@model_validate(EndpointListQuery)
|
||||
def get(self, req_data: EndpointListQuery, tenant_id: str, user_id: str):
|
||||
def get(self, tenant_id: str, user_id: str):
|
||||
args = EndpointListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
endpoints = EndpointService.list_endpoints(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
page=req_data.page,
|
||||
page_size=req_data.page_size,
|
||||
page=args.page,
|
||||
page_size=args.page_size,
|
||||
)
|
||||
|
||||
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
|
||||
@@ -321,15 +322,15 @@ class EndpointListForSinglePluginApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
@model_validate(EndpointListForPluginQuery)
|
||||
def get(self, req_data: EndpointListForPluginQuery, tenant_id: str, user_id: str):
|
||||
def get(self, tenant_id: str, user_id: str):
|
||||
args = EndpointListForPluginQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
endpoints = EndpointService.list_endpoints_for_single_plugin(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
plugin_id=req_data.plugin_id,
|
||||
page=req_data.page,
|
||||
page_size=req_data.page_size,
|
||||
plugin_id=args.plugin_id,
|
||||
page=args.page,
|
||||
page_size=args.page_size,
|
||||
)
|
||||
|
||||
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -17,7 +18,6 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
is_admin_or_owner_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -212,12 +212,12 @@ class DefaultModelApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserGetDefault)
|
||||
def get(self, req_data: ParserGetDefault, tenant_id: str):
|
||||
def get(self, tenant_id: str):
|
||||
args = ParserGetDefault.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
default_model_entity = model_provider_service.get_default_model_of_model_type(
|
||||
tenant_id=tenant_id, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, model_type=args.model_type
|
||||
)
|
||||
|
||||
return DefaultModelDataResponse(data=default_model_entity).model_dump(mode="json")
|
||||
@@ -230,10 +230,10 @@ class DefaultModelApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPostDefault)
|
||||
def post(self, req_data: ParserPostDefault, tenant_id: str):
|
||||
def post(self, tenant_id: str):
|
||||
args = ParserPostDefault.model_validate(console_ns.payload)
|
||||
model_provider_service = ModelProviderService()
|
||||
model_settings = req_data.model_settings
|
||||
model_settings = args.model_settings
|
||||
for model_setting in model_settings:
|
||||
if model_setting.provider is None:
|
||||
continue
|
||||
@@ -279,43 +279,43 @@ class ModelProviderModelApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserPostModels)
|
||||
def post(self, req_data: ParserPostModels, tenant_id: str, provider: str):
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
# To save the model's load balance configs
|
||||
args = ParserPostModels.model_validate(console_ns.payload)
|
||||
|
||||
if req_data.config_from == "custom-model":
|
||||
if not req_data.credential_id:
|
||||
if args.config_from == "custom-model":
|
||||
if not args.credential_id:
|
||||
raise ValueError("credential_id is required when configuring a custom-model")
|
||||
service = ModelProviderService()
|
||||
service.switch_active_custom_model_credential(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.credential_id,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
)
|
||||
|
||||
model_load_balancing_service = ModelLoadBalancingService()
|
||||
|
||||
if req_data.load_balancing and req_data.load_balancing.configs:
|
||||
if args.load_balancing and args.load_balancing.configs:
|
||||
# save load balancing configs
|
||||
model_load_balancing_service.update_load_balancing_configs(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
configs=req_data.load_balancing.configs,
|
||||
config_from=req_data.config_from or "",
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
configs=args.load_balancing.configs,
|
||||
config_from=args.config_from or "",
|
||||
session=db.session(),
|
||||
)
|
||||
|
||||
if req_data.load_balancing.enabled:
|
||||
if args.load_balancing.enabled:
|
||||
model_load_balancing_service.enable_model_load_balancing(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
)
|
||||
else:
|
||||
model_load_balancing_service.disable_model_load_balancing(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
)
|
||||
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
@@ -328,12 +328,12 @@ class ModelProviderModelApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserDeleteModels)
|
||||
def delete(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
def delete(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.remove_model(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
)
|
||||
|
||||
return "", 204
|
||||
@@ -352,29 +352,29 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserGetCredentials)
|
||||
def get(self, req_data: ParserGetCredentials, tenant_id: str, user: Account, provider: str):
|
||||
def get(self, tenant_id: str, user: Account, provider: str):
|
||||
args = ParserGetCredentials.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
current_credential = model_provider_service.get_model_credential(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.credential_id,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
)
|
||||
|
||||
model_load_balancing_service = ModelLoadBalancingService()
|
||||
is_load_balancing_enabled, load_balancing_configs = model_load_balancing_service.get_load_balancing_configs(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
session=db.session(),
|
||||
config_from=req_data.config_from or "",
|
||||
config_from=args.config_from or "",
|
||||
)
|
||||
|
||||
if req_data.config_from == "predefined-model":
|
||||
if args.config_from == "predefined-model":
|
||||
# Only the predefined-model branch needs visibility filtering by user.
|
||||
# The account is injected once by the handler and only passed into the
|
||||
# service branch that needs user-scoped credential visibility.
|
||||
@@ -387,8 +387,8 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
available_credentials = model_provider_service.get_provider_model_available_credentials(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
)
|
||||
|
||||
credentials: dict[str, Any] = {}
|
||||
@@ -414,8 +414,8 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserCreateCredential)
|
||||
def post(self, req_data: ParserCreateCredential, tenant_id: str, provider: str):
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
args = ParserCreateCredential.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
@@ -423,17 +423,17 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
model_provider_service.create_model_credential(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
credentials=req_data.credentials,
|
||||
credential_name=req_data.name,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
credentials=args.credentials,
|
||||
credential_name=args.name,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
logger.exception(
|
||||
"Failed to save model credentials, tenant_id: %s, model: %s, model_type: %s",
|
||||
tenant_id,
|
||||
req_data.model,
|
||||
req_data.model_type,
|
||||
args.model,
|
||||
args.model_type,
|
||||
)
|
||||
raise ValueError(str(ex))
|
||||
|
||||
@@ -447,8 +447,8 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserUpdateCredential)
|
||||
def put(self, req_data: ParserUpdateCredential, current_tenant_id: str, provider: str):
|
||||
def put(self, current_tenant_id: str, provider: str):
|
||||
args = ParserUpdateCredential.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
@@ -456,11 +456,11 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
model_provider_service.update_model_credential(
|
||||
tenant_id=current_tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credentials=req_data.credentials,
|
||||
credential_id=req_data.credential_id,
|
||||
credential_name=req_data.name,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credentials=args.credentials,
|
||||
credential_id=args.credential_id,
|
||||
credential_name=args.name,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
raise ValueError(str(ex))
|
||||
@@ -475,16 +475,16 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserDeleteCredential)
|
||||
def delete(self, req_data: ParserDeleteCredential, current_tenant_id: str, provider: str):
|
||||
def delete(self, current_tenant_id: str, provider: str):
|
||||
args = ParserDeleteCredential.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.remove_model_credential(
|
||||
tenant_id=current_tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.credential_id,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
)
|
||||
|
||||
return "", 204
|
||||
@@ -500,16 +500,16 @@ class ModelProviderModelCredentialSwitchApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.CREDENTIAL_USE, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserSwitch)
|
||||
def post(self, req_data: ParserSwitch, current_tenant_id: str, provider: str):
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
args = ParserSwitch.model_validate(console_ns.payload)
|
||||
|
||||
service = ModelProviderService()
|
||||
service.add_model_credential_to_model_list(
|
||||
tenant_id=current_tenant_id,
|
||||
provider=provider,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.credential_id,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
|
||||
@@ -525,12 +525,12 @@ class ModelProviderModelEnableApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@model_validate(ParserDeleteModels)
|
||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.enable_model(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
)
|
||||
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
@@ -547,12 +547,12 @@ class ModelProviderModelDisableApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@model_validate(ParserDeleteModels)
|
||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.disable_model(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
)
|
||||
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
@@ -579,8 +579,8 @@ class ModelProviderModelValidateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserValidate)
|
||||
def post(self, req_data: ParserValidate, tenant_id: str, provider: str):
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
args = ParserValidate.model_validate(console_ns.payload)
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
@@ -591,9 +591,9 @@ class ModelProviderModelValidateApi(Resource):
|
||||
model_provider_service.validate_model_credentials(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
credentials=req_data.credentials,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
credentials=args.credentials,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
result = False
|
||||
@@ -617,12 +617,12 @@ class ModelProviderModelParameterRuleApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(ParserParameter)
|
||||
def get(self, req_data: ParserParameter, tenant_id: str, provider: str):
|
||||
def get(self, tenant_id: str, provider: str):
|
||||
args = ParserParameter.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
parameter_rules = model_provider_service.get_model_parameter_rules(
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model
|
||||
tenant_id=tenant_id, provider=provider, model=args.model
|
||||
)
|
||||
|
||||
return ModelParameterRuleListResponse(data=parameter_rules).model_dump(mode="json")
|
||||
|
||||
@@ -11,7 +11,7 @@ from werkzeug.exceptions import NotFound
|
||||
from configs import dify_config
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, rbac_permission_required
|
||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, rbac_permission_required
|
||||
from core.db.session_factory import session_factory
|
||||
from core.rbac import RBACResourceWhitelistScope
|
||||
from extensions.ext_database import db
|
||||
@@ -310,19 +310,19 @@ class RBACRolesApi(Resource):
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.WORKSPACE_ROLE_MANAGE, resource_required=False
|
||||
)
|
||||
@console_ns.response(200, "Success", console_ns.models[_RBACRoleList.__name__])
|
||||
@model_validate(_RolesListQuery)
|
||||
def get(self, req_data: _RolesListQuery):
|
||||
def get(self):
|
||||
tenant_id, account_id = _current_ids()
|
||||
options = req_data.to_inner_options()
|
||||
query = _RolesListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
options = query.to_inner_options()
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
result = _legacy_workspace_roles(
|
||||
options, include_owner=req_data.include_owner, billing_enabled=dify_config.BILLING_ENABLED
|
||||
options, include_owner=query.include_owner, billing_enabled=dify_config.BILLING_ENABLED
|
||||
)
|
||||
else:
|
||||
result = svc.RBACService.Roles.list(
|
||||
tenant_id,
|
||||
account_id,
|
||||
include_owner=req_data.include_owner,
|
||||
include_owner=query.include_owner,
|
||||
biiling_enabled=dify_config.BILLING_ENABLED,
|
||||
options=options,
|
||||
)
|
||||
|
||||
@@ -39,7 +39,6 @@ from ..wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
is_admin_or_owner_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -234,12 +233,13 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderCreatePayload)
|
||||
def post(self, req_data: TriggerSubscriptionBuilderCreatePayload, tenant_id: str, user: Account, provider: str):
|
||||
def post(self, tenant_id: str, user: Account, provider: str):
|
||||
"""Add a new subscription instance for a trigger provider"""
|
||||
|
||||
payload = TriggerSubscriptionBuilderCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
credential_type = CredentialType.of(req_data.credential_type)
|
||||
credential_type = CredentialType.of(payload.credential_type)
|
||||
subscription_builder = TriggerSubscriptionBuilderService.create_trigger_subscription_builder(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user.id,
|
||||
@@ -298,17 +298,11 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderVerifyPayload,
|
||||
tenant_id: str,
|
||||
user: Account,
|
||||
provider: str,
|
||||
subscription_builder_id: str,
|
||||
):
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
"""Verify and update a subscription instance for a trigger provider"""
|
||||
|
||||
payload = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
# Use atomic update_and_verify to prevent race conditions
|
||||
result = TriggerSubscriptionBuilderService.update_and_verify_builder(
|
||||
@@ -317,7 +311,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
credentials=req_data.credentials,
|
||||
credentials=payload.credentials,
|
||||
),
|
||||
)
|
||||
return dump_response(TriggerVerificationResponse, result)
|
||||
@@ -343,17 +337,10 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderUpdatePayload,
|
||||
tenant_id: str,
|
||||
user: Account,
|
||||
provider: str,
|
||||
subscription_builder_id: str,
|
||||
):
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
"""Update a subscription instance for a trigger provider"""
|
||||
|
||||
payload = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
|
||||
try:
|
||||
return TriggerSubscriptionBuilderService.update_trigger_subscription_builder(
|
||||
tenant_id=tenant_id,
|
||||
@@ -361,10 +348,10 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
name=req_data.name,
|
||||
parameters=req_data.parameters,
|
||||
properties=req_data.properties,
|
||||
credentials=req_data.credentials,
|
||||
name=payload.name,
|
||||
parameters=payload.parameters,
|
||||
properties=payload.properties,
|
||||
credentials=payload.credentials,
|
||||
),
|
||||
).model_dump(mode="json")
|
||||
except Exception as e:
|
||||
@@ -419,16 +406,9 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderUpdatePayload,
|
||||
tenant_id: str,
|
||||
user: Account,
|
||||
provider: str,
|
||||
subscription_builder_id: str,
|
||||
):
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
"""Build a subscription instance for a trigger provider"""
|
||||
payload = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
|
||||
try:
|
||||
# Use atomic update_and_build to prevent race conditions
|
||||
TriggerSubscriptionBuilderService.update_and_build_builder(
|
||||
@@ -437,9 +417,9 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
name=req_data.name,
|
||||
parameters=req_data.parameters,
|
||||
properties=req_data.properties,
|
||||
name=payload.name,
|
||||
parameters=payload.parameters,
|
||||
properties=payload.properties,
|
||||
),
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
@@ -462,10 +442,11 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(self, req_data: TriggerSubscriptionBuilderUpdatePayload, tenant_id: str, subscription_id: str):
|
||||
def post(self, tenant_id: str, subscription_id: str):
|
||||
"""Update a subscription instance"""
|
||||
|
||||
request = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
subscription = TriggerProviderService.get_subscription_by_id(
|
||||
tenant_id=tenant_id,
|
||||
subscription_id=subscription_id,
|
||||
@@ -477,9 +458,7 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
|
||||
try:
|
||||
# For rename only, just update the name
|
||||
rename = req_data.name is not None and not any(
|
||||
(req_data.credentials, req_data.parameters, req_data.properties)
|
||||
)
|
||||
rename = request.name is not None and not any((request.credentials, request.parameters, request.properties))
|
||||
# When credential type is UNAUTHORIZED, it indicates the subscription was manually created
|
||||
# For Manually created subscription, they dont have credentials, parameters
|
||||
# They only have name and properties(which is input by user)
|
||||
@@ -488,8 +467,8 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
TriggerProviderService.update_trigger_subscription(
|
||||
tenant_id=tenant_id,
|
||||
subscription_id=subscription_id,
|
||||
name=req_data.name,
|
||||
properties=req_data.properties,
|
||||
name=request.name,
|
||||
properties=request.properties,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
|
||||
@@ -497,11 +476,11 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
# we need to call third party provider(e.g. GitHub) to rebuild the subscription
|
||||
TriggerProviderService.rebuild_trigger_subscription(
|
||||
tenant_id=tenant_id,
|
||||
name=req_data.name,
|
||||
name=request.name,
|
||||
provider_id=provider_id,
|
||||
subscription_id=subscription_id,
|
||||
credentials=req_data.credentials or subscription.credentials,
|
||||
parameters=req_data.parameters or subscription.parameters,
|
||||
credentials=request.credentials or subscription.credentials,
|
||||
parameters=request.parameters or subscription.parameters,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
except ValueError as e:
|
||||
@@ -761,17 +740,18 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerOAuthClientPayload)
|
||||
def post(self, req_data: TriggerOAuthClientPayload, tenant_id: str, provider: str):
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
"""Configure custom OAuth client for a provider"""
|
||||
|
||||
payload = TriggerOAuthClientPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
provider_id = TriggerProviderID(provider)
|
||||
result = TriggerProviderService.save_custom_oauth_client_params(
|
||||
tenant_id=tenant_id,
|
||||
provider_id=provider_id,
|
||||
client_params=req_data.client_params,
|
||||
enabled=req_data.enabled,
|
||||
client_params=payload.client_params,
|
||||
enabled=payload.enabled,
|
||||
)
|
||||
return dump_response(SimpleResultResponse, result)
|
||||
|
||||
@@ -825,24 +805,18 @@ class TriggerSubscriptionVerifyApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderVerifyPayload,
|
||||
tenant_id: str,
|
||||
user: Account,
|
||||
provider: str,
|
||||
subscription_id: str,
|
||||
):
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_id: str):
|
||||
"""Verify credentials for an existing subscription (edit mode only)"""
|
||||
|
||||
verify_request = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
result = TriggerProviderService.verify_subscription_credentials(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user.id,
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_id=subscription_id,
|
||||
credentials=req_data.credentials,
|
||||
credentials=verify_request.credentials,
|
||||
)
|
||||
return dump_response(TriggerVerificationResponse, result)
|
||||
except ValueError as e:
|
||||
|
||||
@@ -538,14 +538,8 @@ class AgentModelResponseFormatConfig(AgentFlexibleConfig):
|
||||
type: str | None = Field(default=None, max_length=64)
|
||||
|
||||
|
||||
class AgentSoulModelSettings(AgentFlexibleConfig):
|
||||
"""Model parameters for the Agent Soul model.
|
||||
|
||||
Model plugins can declare arbitrary parameters via ``parameter_rules``
|
||||
(e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
|
||||
fields typed below, so extra keys must round-trip through persistence
|
||||
rather than being dropped.
|
||||
"""
|
||||
class AgentSoulModelSettings(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
temperature: float | None = None
|
||||
top_p: float | None = None
|
||||
|
||||
@@ -14933,13 +14933,6 @@ Reference to model credentials resolved only at runtime.
|
||||
|
||||
#### AgentSoulModelSettings
|
||||
|
||||
Model parameters for the Agent Soul model.
|
||||
|
||||
Model plugins can declare arbitrary parameters via ``parameter_rules``
|
||||
(e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
|
||||
fields typed below, so extra keys must round-trip through persistence
|
||||
rather than being dropped.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| frequency_penalty | number | | No |
|
||||
|
||||
@@ -7,7 +7,7 @@ from uuid import uuid4
|
||||
from flask import Flask
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from controllers.console.datasets.data_source import DataSourceNotionListApi, DataSourceNotionListQuery
|
||||
from controllers.console.datasets.data_source import DataSourceNotionListApi
|
||||
from models import Account
|
||||
from models.dataset import Document
|
||||
from models.enums import DataSourceType, DocumentCreatedFrom, IndexingStatus
|
||||
@@ -78,11 +78,7 @@ def test_notion_page_is_marked_bound_from_persisted_document(
|
||||
),
|
||||
):
|
||||
response, status = unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="c1", dataset_id=dataset_id),
|
||||
db_session_with_containers,
|
||||
tenant_id,
|
||||
account,
|
||||
DataSourceNotionListApi(), db_session_with_containers, tenant_id, account
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -119,29 +119,6 @@ def test_request_builder_separates_agent_soul_and_workflow_job_prompt():
|
||||
assert dumped["composition"]["layers"][2]["config"]["user"] == "Summarize the report."
|
||||
|
||||
|
||||
def test_request_builder_forwards_plugin_specific_model_settings_via_extra_body():
|
||||
run_input = _run_input().model_copy(
|
||||
update={
|
||||
"model": AgentBackendModelConfig(
|
||||
plugin_id="langgenius/tongyi",
|
||||
model_provider="tongyi",
|
||||
model="qwen-plus-latest",
|
||||
credentials={"api_key": "secret-key"},
|
||||
model_settings={"temperature": 0.7, "enable_thinking": True, "thinking_budget": 4096},
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
request = AgentBackendRunRequestBuilder().build_for_workflow_node(run_input)
|
||||
layers = {layer.name: layer for layer in request.composition.layers}
|
||||
model_config = cast(DifyPluginLLMLayerConfig, layers[DIFY_AGENT_MODEL_LAYER_ID].config)
|
||||
|
||||
assert model_config.model_settings == {
|
||||
"temperature": 0.7,
|
||||
"extra_body": {"enable_thinking": True, "thinking_budget": 4096},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("agent_config_version_kind", ["snapshot", "draft"])
|
||||
def test_agent_app_request_builder_keeps_agent_soul_prompt_for_snapshot_and_draft(
|
||||
agent_config_version_kind: str,
|
||||
|
||||
@@ -59,7 +59,6 @@ from controllers.console.app.workflow import AdvancedChatWorkflowRunPayload, Syn
|
||||
from controllers.console.app.workflow_app_log import WorkflowAppLogQuery
|
||||
from controllers.console.app.workflow_draft_variable import (
|
||||
EnvironmentVariableUpdatePayload,
|
||||
WorkflowDraftVariableListQuery,
|
||||
WorkflowDraftVariableUpdatePayload,
|
||||
)
|
||||
from controllers.console.app.workflow_statistic import WorkflowStatisticQuery
|
||||
@@ -145,13 +144,7 @@ class TestCompletionEndpoints:
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
):
|
||||
resp = method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
resp = method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
|
||||
assert resp == {"result": {"text": "ok"}}
|
||||
|
||||
@@ -174,13 +167,7 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
|
||||
def test_completion_api_provider_not_initialized(
|
||||
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
|
||||
@@ -199,13 +186,7 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(completion_module.ProviderNotInitializeError),
|
||||
):
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
|
||||
def test_completion_api_quota_exceeded(
|
||||
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
|
||||
@@ -224,13 +205,7 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(completion_module.ProviderQuotaExceededError),
|
||||
):
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
|
||||
|
||||
class TestAppEndpoints:
|
||||
@@ -257,17 +232,7 @@ class TestAppEndpoints:
|
||||
app.test_request_context("/console/api/apps/app-1", method="PUT", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
response = method(
|
||||
api,
|
||||
app_module.UpdateAppPayload(
|
||||
name="Updated App",
|
||||
description="Updated description",
|
||||
icon="🤖",
|
||||
icon_background="#FFFFFF",
|
||||
),
|
||||
unbound_session,
|
||||
app_model=_make_app(icon_type=app_module.IconType.EMOJI),
|
||||
)
|
||||
response = method(api, unbound_session, app_model=_make_app(icon_type=app_module.IconType.EMOJI))
|
||||
|
||||
assert response == {"id": "app-1"}
|
||||
assert app_service.update_app.call_args.args[1]["icon_type"] is None
|
||||
@@ -306,16 +271,7 @@ class TestAppEndpoints:
|
||||
app.test_request_context("/console/api/apps/app-1/icon", method="POST", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
response = method(
|
||||
api,
|
||||
app_module.AppIconPayload(
|
||||
icon="https://example.com/icon.png",
|
||||
icon_type=app_module.IconType.IMAGE,
|
||||
icon_background="#FFFFFF",
|
||||
),
|
||||
unbound_session,
|
||||
app_model=_make_app(),
|
||||
)
|
||||
response = method(api, unbound_session, app_model=_make_app())
|
||||
|
||||
assert response == {"id": "app-1"}
|
||||
assert app_service.update_app_icon.call_args.args[1:] == (
|
||||
@@ -422,13 +378,7 @@ class TestSiteEndpoints:
|
||||
site = self._add_site(db.session)
|
||||
|
||||
with database_app.test_request_context("/", json={"title": "My Site", "input_placeholder": "Ask me anything"}):
|
||||
result = method(
|
||||
api,
|
||||
AppSiteUpdatePayload(title="My Site", input_placeholder="Ask me anything"),
|
||||
db.session,
|
||||
_make_account(),
|
||||
app_model=_make_app(),
|
||||
)
|
||||
result = method(api, db.session, _make_account(), app_model=_make_app())
|
||||
|
||||
db.session.refresh(site)
|
||||
assert isinstance(result, dict)
|
||||
@@ -495,7 +445,7 @@ class TestWorkflowAppLogEndpoints:
|
||||
)
|
||||
|
||||
with database_app.test_request_context("/?page=1&limit=20"):
|
||||
result = method(api, WorkflowAppLogQuery(page=1, limit=20), app_model=_make_app("app-1"))
|
||||
result = method(api, app_model=_make_app("app-1"))
|
||||
|
||||
assert result == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||
|
||||
@@ -525,12 +475,7 @@ class TestWorkflowDraftVariableEndpoints:
|
||||
monkeypatch.setattr(workflow_draft_variable_module, "WorkflowService", DummyWorkflowService)
|
||||
|
||||
with database_app.test_request_context("/?page=1&limit=20"):
|
||||
result = method(
|
||||
api,
|
||||
WorkflowDraftVariableListQuery(page=1, limit=20),
|
||||
_make_account(),
|
||||
app_model=_make_app("app-1"),
|
||||
)
|
||||
result = method(api, _make_account(), app_model=_make_app("app-1"))
|
||||
|
||||
assert result == {"items": [], "total": 0}
|
||||
|
||||
@@ -583,16 +528,7 @@ class TestWorkflowDraftVariableEndpoints:
|
||||
"deleted_environment_variable_ids": ["env-b"],
|
||||
},
|
||||
):
|
||||
result = method(
|
||||
api,
|
||||
EnvironmentVariableUpdatePayload(
|
||||
environment_variables=[{"id": "env-a", "name": "a", "value_type": "string", "value": "new-a"}],
|
||||
patch=True,
|
||||
deleted_environment_variable_ids=["env-b"],
|
||||
),
|
||||
_make_account(),
|
||||
app_model=_make_app(),
|
||||
)
|
||||
result = method(api, _make_account(), app_model=_make_app())
|
||||
|
||||
assert result == {"result": "success"}
|
||||
assert [(variable.id, variable.value) for variable in captured["environment_variables"]] == [("env-a", "new-a")]
|
||||
@@ -638,7 +574,7 @@ class TestWorkflowStatisticEndpoints:
|
||||
with database_app.test_request_context("/"):
|
||||
account = _make_account()
|
||||
account.timezone = "UTC"
|
||||
response = method(api, WorkflowStatisticQuery(), account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
response = method(api, account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
|
||||
assert response.get_json() == {"data": [{"date": "2024-01-01"}]}
|
||||
|
||||
@@ -668,7 +604,7 @@ class TestWorkflowStatisticEndpoints:
|
||||
with database_app.test_request_context("/"):
|
||||
account = _make_account()
|
||||
account.timezone = "UTC"
|
||||
response = method(api, WorkflowStatisticQuery(), account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
response = method(api, account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
|
||||
assert response.get_json() == {"data": [{"date": "2024-01-02"}]}
|
||||
|
||||
@@ -698,7 +634,7 @@ class TestWorkflowTriggerEndpoints:
|
||||
db.session.commit()
|
||||
|
||||
with database_app.test_request_context("/?node_id=node-1"):
|
||||
result = method(api, Parser(node_id="node-1"), app_model=_make_app())
|
||||
result = method(api, app_model=_make_app())
|
||||
|
||||
assert isinstance(result, dict)
|
||||
assert {"id", "webhook_id", "webhook_url", "webhook_debug_url", "node_id", "created_at"} <= set(result.keys())
|
||||
|
||||
@@ -159,7 +159,7 @@ class TestAppImportApi:
|
||||
)
|
||||
|
||||
with app.test_request_context("/console/api/apps/imports", method="POST", json={"mode": "yaml-content"}):
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _make_account())
|
||||
response, status = method(api, _make_account())
|
||||
|
||||
assert transaction_events.rollbacks == 1
|
||||
assert transaction_events.commits == 0
|
||||
@@ -185,7 +185,7 @@ class TestAppImportApi:
|
||||
)
|
||||
|
||||
with app.test_request_context("/console/api/apps/imports", method="POST", json={"mode": "yaml-content"}):
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _make_account())
|
||||
response, status = method(api, _make_account())
|
||||
|
||||
assert transaction_events.commits == 1
|
||||
assert transaction_events.rollbacks == 0
|
||||
@@ -213,7 +213,7 @@ class TestAppImportApi:
|
||||
monkeypatch.setattr(app_import_module.EnterpriseService.WebAppAuth, "update_app_access_mode", update_access)
|
||||
|
||||
with app.test_request_context("/console/api/apps/imports", method="POST", json={"mode": "yaml-content"}):
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _make_account())
|
||||
response, status = method(api, _make_account())
|
||||
|
||||
assert transaction_events.commits == 1
|
||||
assert transaction_events.rollbacks == 0
|
||||
@@ -251,7 +251,7 @@ class TestAppImportApi:
|
||||
)
|
||||
|
||||
with app.test_request_context("/console/api/apps/imports", method="POST", json={"mode": "yaml-content"}):
|
||||
response, status = method(app_import_module.AppImportPayload(mode="yaml-content"))
|
||||
response, status = method()
|
||||
|
||||
assert transaction_events.commits == 1
|
||||
_assert_app_persistence(sqlite_app_engine, app_id, persisted=True)
|
||||
@@ -291,7 +291,7 @@ class TestAppImportApi:
|
||||
method="POST",
|
||||
json={"mode": "yaml-content", "app_id": "existing-app"},
|
||||
):
|
||||
response, status = method(app_import_module.AppImportPayload(mode="yaml-content", app_id="existing-app"))
|
||||
response, status = method()
|
||||
|
||||
assert transaction_events.commits == 1
|
||||
_assert_app_persistence(sqlite_app_engine, app_id, persisted=True)
|
||||
|
||||
@@ -331,9 +331,19 @@ def test_app_list_query_accepts_single_repeated_tag_id(app_module):
|
||||
assert query.tag_ids == [tag_id]
|
||||
|
||||
|
||||
def test_create_app_endpoint_rejects_agent_mode(app_module):
|
||||
with pytest.raises(ValidationError):
|
||||
app_module.CreateAppPayload.model_validate({"name": "Iris", "mode": "agent", "description": "Agent app"})
|
||||
def test_create_app_endpoint_rejects_agent_mode(app_module, monkeypatch: pytest.MonkeyPatch, unbound_session: Session):
|
||||
payload = {"name": "Iris", "mode": "agent", "description": "Agent app"}
|
||||
app_service = MagicMock()
|
||||
monkeypatch.setattr(app_module, "AppService", lambda: app_service)
|
||||
|
||||
app_module.console_ns.payload = payload
|
||||
try:
|
||||
with pytest.raises(ValidationError):
|
||||
_unwrap(app_module.AppListApi().post)(unbound_session, "tenant-1", SimpleNamespace(id="account-1"))
|
||||
finally:
|
||||
app_module.console_ns.payload = None
|
||||
|
||||
app_service.create_app.assert_not_called()
|
||||
|
||||
|
||||
def test_app_partial_serialization_uses_aliases(app_models):
|
||||
@@ -640,17 +650,7 @@ def test_app_create_api_attaches_permission_keys(app, app_module, unbound_sessio
|
||||
replace_whitelist,
|
||||
)
|
||||
|
||||
resp, status = method(
|
||||
app_module.AppListApi(),
|
||||
app_module.CreateAppPayload(
|
||||
name="Created App",
|
||||
description="Summary",
|
||||
mode="advanced-chat",
|
||||
),
|
||||
unbound_session,
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
)
|
||||
resp, status = method(app_module.AppListApi(), unbound_session, "tenant-1", SimpleNamespace(id="acct-1"))
|
||||
|
||||
assert status == 201
|
||||
assert resp["permission_keys"] == ["app.acl.view_layout", "app.acl.edit"]
|
||||
@@ -1076,7 +1076,6 @@ def test_app_copy_api_attaches_permission_keys(app, app_module, sqlite_session:
|
||||
|
||||
resp, status = method(
|
||||
app_module.AppCopyApi(),
|
||||
app_module.CopyAppPayload(),
|
||||
"tenant-1",
|
||||
SimpleNamespace(id="acct-1"),
|
||||
app_model=SimpleNamespace(id="app-original"),
|
||||
|
||||
@@ -17,8 +17,6 @@ from controllers.console.app.audio import (
|
||||
ChatMessageAudioApi,
|
||||
ChatMessageTextApi,
|
||||
TextModesApi,
|
||||
TextToSpeechPayload,
|
||||
TextToSpeechVoiceQuery,
|
||||
)
|
||||
from controllers.console.app.error import (
|
||||
AppUnavailableError,
|
||||
@@ -292,7 +290,7 @@ def test_console_text_api_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -
|
||||
method="POST",
|
||||
json={"text": "hello", "voice": "v"},
|
||||
):
|
||||
response = handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
response = handler(api, app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
|
||||
@@ -317,7 +315,7 @@ def test_console_text_api_builds_message_ref(app: Flask, monkeypatch: pytest.Mon
|
||||
),
|
||||
patch("controllers.console.app.audio.current_user", SimpleNamespace(id="account-1")),
|
||||
):
|
||||
response = handler(api, TextToSpeechPayload(text="hello", message_id="message-1"), app_model=app_model)
|
||||
response = handler(api, app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
assert calls["message_ref"] == MessageRef(AppRef("tenant-1", "app-1"), "message-1", account_id="account-1")
|
||||
@@ -336,7 +334,7 @@ def test_console_text_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
json={"text": "hello"},
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
handler(api, app_model=app_model)
|
||||
|
||||
|
||||
def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -348,7 +346,7 @@ def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch)
|
||||
app_model = SimpleNamespace(tenant_id="t1")
|
||||
|
||||
with app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"):
|
||||
response = handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
response = handler(api, app_model=app_model)
|
||||
|
||||
assert response == expected_voices
|
||||
|
||||
@@ -366,7 +364,7 @@ def test_console_text_modes_language_error(app: Flask, monkeypatch: pytest.Monke
|
||||
|
||||
with app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
handler(api, app_model=app_model)
|
||||
|
||||
|
||||
def test_audio_to_text_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -426,7 +424,7 @@ def test_text_to_audio_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> N
|
||||
method="POST",
|
||||
json={"text": "hello"},
|
||||
):
|
||||
response = method(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
response = method(api, app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
|
||||
@@ -445,7 +443,7 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc
|
||||
method="GET",
|
||||
query_string={"language": "en-US"},
|
||||
):
|
||||
response = method(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
response = method(api, app_model=app_model)
|
||||
|
||||
assert response == expected_voices
|
||||
|
||||
@@ -483,7 +481,7 @@ def test_text_to_audio_with_language_param(app: Flask, monkeypatch: pytest.Monke
|
||||
method="POST",
|
||||
json={"text": "hello", "language": "en-US"},
|
||||
):
|
||||
response = method(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
response = method(api, app_model=app_model)
|
||||
assert response == {"audio": "test"}
|
||||
|
||||
|
||||
@@ -503,5 +501,5 @@ def test_text_to_audio_voices_with_language_filter(app: Flask, monkeypatch: pyte
|
||||
"/console/api/apps/app-1/text-to-audio/voices?language=en-US",
|
||||
method="GET",
|
||||
):
|
||||
response = method(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
response = method(api, app_model=app_model)
|
||||
assert isinstance(response, list)
|
||||
|
||||
@@ -58,13 +58,7 @@ def test_completion_conversation_list_returns_paginated_result(
|
||||
paginate_result.items = []
|
||||
monkeypatch.setattr(conversation_module, "paginate_query", lambda *_args, **_kwargs: paginate_result)
|
||||
with app.test_request_context("/console/api/apps/app-1/completion-conversations", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, unbound_session, account, app_model=SimpleNamespace(id="app-1"))
|
||||
assert response == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||
|
||||
|
||||
@@ -83,13 +77,7 @@ def test_completion_conversation_list_invalid_time_range(
|
||||
"/console/api/apps/app-1/completion-conversations", method="GET", query_string={"start": "bad"}
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
method(api, unbound_session, account, app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
|
||||
def test_chat_conversation_list_advanced_chat_calls_paginate(
|
||||
@@ -108,11 +96,7 @@ def test_chat_conversation_list_advanced_chat_calls_paginate(
|
||||
monkeypatch.setattr(conversation_module, "paginate_query", lambda *_args, **_kwargs: paginate_result)
|
||||
with app.test_request_context("/console/api/apps/app-1/chat-conversations", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
conversation_module.ChatConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=SimpleNamespace(id="app-1", mode=AppMode.ADVANCED_CHAT),
|
||||
api, unbound_session, account, app_model=SimpleNamespace(id="app-1", mode=AppMode.ADVANCED_CHAT)
|
||||
)
|
||||
assert response == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||
|
||||
|
||||
@@ -51,11 +51,7 @@ def test_get_conversation_variables_returns_paginated_response(
|
||||
method="GET",
|
||||
query_string={"conversation_id": "conv-1"},
|
||||
):
|
||||
response = method(
|
||||
api,
|
||||
conversation_variables_module.ConversationVariablesQuery(conversation_id="conv-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert response["page"] == 1
|
||||
assert response["limit"] == 100
|
||||
@@ -94,11 +90,7 @@ def test_get_conversation_variables_normalizes_value_type_and_value(
|
||||
method="GET",
|
||||
query_string={"conversation_id": "conv-1"},
|
||||
):
|
||||
response = method(
|
||||
api,
|
||||
conversation_variables_module.ConversationVariablesQuery(conversation_id="conv-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert response["data"][0]["value_type"] == "number"
|
||||
assert response["data"][0]["value"] == "42"
|
||||
@@ -110,4 +102,4 @@ def test_get_conversation_variables_requires_conversation_id(app) -> None:
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/conversation-variables", method="GET"):
|
||||
with pytest.raises(ValidationError):
|
||||
conversation_variables_module.ConversationVariablesQuery.model_validate({})
|
||||
method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
@@ -6,19 +6,11 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from flask import Flask, request
|
||||
from flask import Flask
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from controllers.console.app import generator as generator_module
|
||||
from controllers.console.app.error import ProviderNotInitializeError
|
||||
from controllers.console.app.generator import (
|
||||
InstructionGeneratePayload,
|
||||
InstructionTemplatePayload,
|
||||
RuleCodeGeneratePayload,
|
||||
RuleGeneratePayload,
|
||||
WorkflowGeneratePayload,
|
||||
WorkflowInstructionSuggestionsPayload,
|
||||
)
|
||||
from core.errors.error import ProviderTokenNotInitError
|
||||
from models.model import App, AppMode
|
||||
|
||||
@@ -69,7 +61,7 @@ def test_rule_generate_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> N
|
||||
method="POST",
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
):
|
||||
response = method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
|
||||
assert response == {"rules": []}
|
||||
|
||||
@@ -89,7 +81,7 @@ def test_rule_code_generate_maps_token_error(app: Flask, monkeypatch: pytest.Mon
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(App,)], indirect=True)
|
||||
@@ -108,9 +100,7 @@ def test_instruction_generate_app_not_found(app: Flask, sqlite_session: Session)
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "app app-1 not found"
|
||||
@@ -137,9 +127,7 @@ def test_instruction_generate_workflow_not_found(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "workflow app-1 not found"
|
||||
@@ -167,9 +155,7 @@ def test_instruction_generate_node_missing(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "node node-1 not found"
|
||||
@@ -202,7 +188,7 @@ def test_instruction_generate_code_node(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response = method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
response = method(api, sqlite_session, "t1")
|
||||
|
||||
assert response == {"code": "x"}
|
||||
assert workflow_service.app_model is app_model
|
||||
@@ -232,7 +218,7 @@ def test_instruction_generate_legacy_modify(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response = method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
response = method(api, sqlite_session, "t1")
|
||||
|
||||
assert response == {"instruction": "ok"}
|
||||
|
||||
@@ -252,9 +238,7 @@ def test_instruction_generate_incompatible_params(app: Flask, sqlite_session: Se
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "incompatible parameters"
|
||||
@@ -269,7 +253,7 @@ def test_instruction_template_prompt(app: Flask) -> None:
|
||||
method="POST",
|
||||
json={"type": "prompt"},
|
||||
):
|
||||
response = method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
response = method(api)
|
||||
|
||||
assert "data" in response
|
||||
|
||||
@@ -284,7 +268,7 @@ def test_instruction_template_invalid_type(app: Flask) -> None:
|
||||
json={"type": "unknown"},
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
method(api)
|
||||
|
||||
|
||||
# ─ /workflow-generate ─────────────────────────────────────────────────────────
|
||||
@@ -347,7 +331,7 @@ def test_workflow_generate_returns_service_result(app: Flask, monkeypatch: pytes
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
|
||||
assert response == expected
|
||||
|
||||
@@ -377,7 +361,7 @@ def test_workflow_generate_maps_provider_token_error(app: Flask, monkeypatch: py
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -395,7 +379,7 @@ def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.Monk
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -413,7 +397,7 @@ def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch:
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -431,7 +415,7 @@ def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.Mon
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_accepts_advanced_chat_mode(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -458,7 +442,7 @@ def test_workflow_generate_accepts_advanced_chat_mode(app: Flask, monkeypatch: p
|
||||
method="POST",
|
||||
json=payload,
|
||||
):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
assert captured["mode"] == "advanced-chat"
|
||||
assert captured["instruction"] == "Summarize a URL"
|
||||
@@ -490,7 +474,7 @@ def test_workflow_generate_forwards_current_graph_for_refine(app: Flask, monkeyp
|
||||
method="POST",
|
||||
json=payload,
|
||||
):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
assert captured["current_graph"] == graph
|
||||
|
||||
@@ -517,7 +501,7 @@ def test_workflow_generate_current_graph_defaults_to_none(app: Flask, monkeypatc
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
assert captured["current_graph"] is None
|
||||
|
||||
@@ -544,7 +528,7 @@ def test_workflow_generate_accepts_auto_mode(app: Flask, monkeypatch: pytest.Mon
|
||||
payload = _workflow_generate_payload()
|
||||
payload["mode"] = "auto"
|
||||
with app.test_request_context("/console/api/workflow-generate", method="POST", json=payload):
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
|
||||
assert captured["mode"] == "auto"
|
||||
assert response["mode"] == "advanced-chat"
|
||||
@@ -624,7 +608,7 @@ def test_workflow_instruction_suggestions_route_returns_list(app: Flask, monkeyp
|
||||
method="POST",
|
||||
json={"mode": "workflow", "language": "French", "count": 3},
|
||||
):
|
||||
response = method(api, WorkflowInstructionSuggestionsPayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
|
||||
assert response == {"suggestions": ["Summarize a URL", "Translate text"]}
|
||||
assert captured["mode"] == "workflow"
|
||||
@@ -648,7 +632,7 @@ def test_workflow_instruction_suggestions_route_empty_is_valid_200(app: Flask, m
|
||||
method="POST",
|
||||
json={"mode": "advanced-chat"},
|
||||
):
|
||||
response = method(api, WorkflowInstructionSuggestionsPayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
|
||||
assert response == {"suggestions": []}
|
||||
|
||||
@@ -683,7 +667,7 @@ def test_workflow_generate_stream_emits_plan_then_result(app: Flask, monkeypatch
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
assert response.mimetype == "text/event-stream"
|
||||
frames = _read_sse_frames(response)
|
||||
|
||||
@@ -710,7 +694,7 @@ def test_workflow_generate_stream_provider_error_emits_result_event(
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response = method(api, "t1")
|
||||
frames = _read_sse_frames(response)
|
||||
|
||||
assert len(frames) == 1
|
||||
@@ -727,7 +711,7 @@ def test_workflow_generate_stream_rejects_empty_instruction(app: Flask, monkeypa
|
||||
payload = _workflow_generate_payload()
|
||||
payload["instruction"] = " "
|
||||
with app.test_request_context("/console/api/workflow-generate/stream", method="POST", json=payload):
|
||||
response, status = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
response, status = method(api, "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["errors"][0]["code"] == "EMPTY_INSTRUCTION"
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import pytest
|
||||
from flask import Flask, request
|
||||
from flask import Flask
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from controllers.console.app import generator as generator_module
|
||||
from controllers.console.app.generator import (
|
||||
InstructionGeneratePayload,
|
||||
RuleCodeGeneratePayload,
|
||||
RuleGeneratePayload,
|
||||
RuleStructuredOutputPayload,
|
||||
)
|
||||
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||
from graphon.model_runtime.errors.invoke import InvokeError
|
||||
|
||||
@@ -53,7 +47,7 @@ def test_rule_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_rule_code_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -79,7 +73,7 @@ def test_rule_code_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPat
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
def test_structured_output_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -106,7 +100,7 @@ def test_structured_output_generate_exceptions(app: Flask, monkeypatch: pytest.M
|
||||
json={"instruction": "do it", "model_config": _model_config_payload()},
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, RuleStructuredOutputPayload.model_validate(request.get_json()), "t1")
|
||||
method(api, "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
|
||||
@@ -144,4 +138,4 @@ def test_instruction_generate_exceptions(
|
||||
},
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
method(api, sqlite_session, "t1")
|
||||
|
||||
@@ -53,12 +53,7 @@ def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.Mo
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-01", "message_count": 3}]}
|
||||
|
||||
@@ -72,12 +67,7 @@ def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pyte
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
|
||||
|
||||
@@ -91,12 +81,7 @@ def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 1
|
||||
@@ -114,12 +99,7 @@ def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest.
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-end-users", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-04", "terminal_count": 7}]}
|
||||
|
||||
@@ -146,12 +126,7 @@ def test_daily_message_statistic_with_invalid_time_range(app: Flask, monkeypatch
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
|
||||
def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -167,12 +142,7 @@ def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.M
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 3
|
||||
@@ -186,12 +156,7 @@ def test_daily_message_statistic_empty_result(app: Flask, monkeypatch: pytest.Mo
|
||||
_install_db(monkeypatch, [])
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert _json_payload(response) == {"data": []}
|
||||
|
||||
@@ -210,12 +175,7 @@ def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: p
|
||||
monkeypatch.setattr(statistic_module, "convert_datetime_to_date", lambda field: field)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
|
||||
|
||||
@@ -232,12 +192,7 @@ def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pyte
|
||||
_install_db(monkeypatch, rows)
|
||||
|
||||
with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"):
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 2
|
||||
|
||||
@@ -98,11 +98,7 @@ def test_workflow_run_list_returns_frontend_history_contract(app: Flask, monkeyp
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/apps/app-1/workflow-runs?limit=10", method="GET"):
|
||||
payload = handler(
|
||||
api,
|
||||
workflow_run_module.WorkflowRunListQuery(limit=10),
|
||||
app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"),
|
||||
)
|
||||
payload = handler(api, app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"))
|
||||
|
||||
response = _serialize_200_response(api.get, payload)
|
||||
|
||||
@@ -143,11 +139,7 @@ def test_advanced_chat_workflow_run_list_keeps_message_fields(app: Flask, monkey
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/apps/app-1/advanced-chat/workflow-runs?limit=1", method="GET"):
|
||||
payload = handler(
|
||||
api,
|
||||
workflow_run_module.WorkflowRunListQuery(limit=1),
|
||||
app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"),
|
||||
)
|
||||
payload = handler(api, app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"))
|
||||
|
||||
response = _serialize_200_response(api.get, payload)
|
||||
|
||||
|
||||
@@ -142,12 +142,7 @@ def test_app_trigger_enable_uses_injected_tenant_id(app: Flask, database_session
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
):
|
||||
response = method(
|
||||
api,
|
||||
workflow_trigger_module.ParserEnable(trigger_id=trigger.id, enable_trigger=True),
|
||||
app_model.tenant_id,
|
||||
app_model,
|
||||
)
|
||||
response = method(api, app_model.tenant_id, app_model)
|
||||
|
||||
assert response["id"] == trigger.id
|
||||
assert response["status"] == "enabled"
|
||||
|
||||
@@ -14,15 +14,9 @@ from controllers.console.datasets.rag_pipeline.datasource_auth import (
|
||||
DatasourceAuthListApi,
|
||||
DatasourceAuthOauthCustomClient,
|
||||
DatasourceAuthUpdateApi,
|
||||
DatasourceCredentialDeletePayload,
|
||||
DatasourceCredentialPayload,
|
||||
DatasourceCredentialUpdatePayload,
|
||||
DatasourceCustomClientPayload,
|
||||
DatasourceDefaultPayload,
|
||||
DatasourceHardCodeAuthListApi,
|
||||
DatasourceOAuthCallback,
|
||||
DatasourcePluginOAuthAuthorizationUrl,
|
||||
DatasourceUpdateNamePayload,
|
||||
DatasourceUpdateProviderNameApi,
|
||||
)
|
||||
from core.plugin.impl.oauth import OAuthHandler
|
||||
@@ -411,8 +405,7 @@ class TestDatasourceAuth:
|
||||
return_value=None,
|
||||
) as add_api_key_provider,
|
||||
):
|
||||
req_data = DatasourceCredentialPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -438,7 +431,7 @@ class TestDatasourceAuth:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
def test_get_success(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -469,7 +462,7 @@ class TestDatasourceAuth:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
def test_get_empty_list(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -506,8 +499,7 @@ class TestDatasourceAuthDeleteApi:
|
||||
return_value=None,
|
||||
) as remove_datasource_credentials,
|
||||
):
|
||||
req_data = DatasourceCredentialDeletePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -530,7 +522,7 @@ class TestDatasourceAuthDeleteApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceCredentialDeletePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceAuthUpdateApi:
|
||||
@@ -553,8 +545,7 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_datasource_credentials,
|
||||
):
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 201
|
||||
@@ -582,8 +573,7 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_mock,
|
||||
):
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
update_mock.assert_called_once()
|
||||
@@ -605,8 +595,7 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 201
|
||||
@@ -626,8 +615,7 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_mock,
|
||||
):
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
update_mock.assert_called_once()
|
||||
@@ -728,8 +716,7 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
) as setup_custom_client,
|
||||
):
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -773,8 +760,7 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -797,8 +783,7 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
) as setup_mock,
|
||||
):
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
setup_mock.assert_called_once()
|
||||
@@ -823,8 +808,7 @@ class TestDatasourceAuthDefaultApi:
|
||||
return_value=None,
|
||||
) as set_default_datasource_provider,
|
||||
):
|
||||
req_data = DatasourceDefaultPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -844,7 +828,7 @@ class TestDatasourceAuthDefaultApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceDefaultPayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceUpdateProviderNameApi:
|
||||
@@ -863,8 +847,7 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
return_value=None,
|
||||
) as update_datasource_provider_name,
|
||||
):
|
||||
req_data = DatasourceUpdateNamePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -888,7 +871,7 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
def test_update_name_missing_credential_id(self, app: Flask):
|
||||
api = DatasourceUpdateProviderNameApi()
|
||||
@@ -901,4 +884,4 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
method(api, "tenant-1", "notion")
|
||||
|
||||
@@ -7,7 +7,6 @@ from flask import Flask
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.datasets.rag_pipeline.datasource_content_preview import (
|
||||
DataSourceContentPreviewApi,
|
||||
Parser,
|
||||
)
|
||||
from models import Account
|
||||
from models.dataset import Pipeline
|
||||
@@ -50,8 +49,7 @@ class TestDataSourceContentPreviewApi:
|
||||
return_value=service_instance,
|
||||
),
|
||||
):
|
||||
req_data = Parser.model_validate(payload)
|
||||
response, status = method(api, req_data, account, pipeline, node_id)
|
||||
response, status = method(api, account, pipeline, node_id)
|
||||
|
||||
service_instance.run_datasource_node_preview.assert_called_once_with(
|
||||
pipeline=pipeline,
|
||||
@@ -82,7 +80,7 @@ class TestDataSourceContentPreviewApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, Parser.model_validate(payload), account, pipeline, "node-1")
|
||||
method(api, account, pipeline, "node-1")
|
||||
|
||||
def test_post_without_credential_id(self, app: Flask):
|
||||
api = DataSourceContentPreviewApi()
|
||||
@@ -108,8 +106,7 @@ class TestDataSourceContentPreviewApi:
|
||||
return_value=service_instance,
|
||||
),
|
||||
):
|
||||
req_data = Parser.model_validate(payload)
|
||||
response, status = method(api, req_data, account, pipeline, "node-1")
|
||||
response, status = method(api, account, pipeline, "node-1")
|
||||
|
||||
service_instance.run_datasource_node_preview.assert_called_once()
|
||||
assert status == 200
|
||||
|
||||
@@ -15,11 +15,8 @@ from controllers.console import console_ns
|
||||
from controllers.console.datasets.rag_pipeline import rag_pipeline as module
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline import (
|
||||
CustomizedPipelineTemplateApi,
|
||||
CustomizedPipelineTemplatePayload,
|
||||
PipelineTemplateDetailApi,
|
||||
PipelineTemplateDetailQuery,
|
||||
PipelineTemplateListApi,
|
||||
PipelineTemplateListQuery,
|
||||
PublishCustomizedPipelineTemplateApi,
|
||||
)
|
||||
from models.account import Account
|
||||
@@ -93,7 +90,7 @@ class TestPipelineTemplateListApi:
|
||||
app.test_request_context("/rag/pipeline/templates"),
|
||||
patch.object(module.RagPipelineService, "get_pipeline_templates", side_effect=get_pipeline_templates),
|
||||
):
|
||||
response, status = method(api, PipelineTemplateListQuery(), session, tenant_id)
|
||||
response, status = method(api, session, tenant_id)
|
||||
|
||||
assert status == 200
|
||||
assert service_calls == [("built-in", "en-US", tenant_id)]
|
||||
@@ -123,9 +120,7 @@ class TestPipelineTemplateListApi:
|
||||
app.test_request_context("/rag/pipeline/templates?type=customized&language=ja-JP"),
|
||||
patch.object(module.RagPipelineService, "get_pipeline_templates", side_effect=get_pipeline_templates),
|
||||
):
|
||||
response, status = method(
|
||||
api, PipelineTemplateListQuery(type="customized", language="ja-JP"), session, tenant_id
|
||||
)
|
||||
response, status = method(api, session, tenant_id)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"pipeline_templates": []}
|
||||
@@ -152,7 +147,7 @@ class TestPipelineTemplateDetailApi:
|
||||
side_effect=get_pipeline_template_detail,
|
||||
),
|
||||
):
|
||||
response, status = method(api, PipelineTemplateDetailQuery(type="customized"), session, "template-1")
|
||||
response, status = method(api, session, "template-1")
|
||||
|
||||
assert status == 200
|
||||
assert response == {**_template_detail(), "created_by": None}
|
||||
@@ -175,7 +170,7 @@ class TestPipelineTemplateDetailApi:
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, PipelineTemplateDetailQuery(), session, "missing")
|
||||
method(api, session, "missing")
|
||||
|
||||
|
||||
class TestCustomizedPipelineTemplateApi:
|
||||
@@ -203,9 +198,7 @@ class TestCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module.RagPipelineService, "update_customized_pipeline_template", side_effect=update_template),
|
||||
):
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "template-1"
|
||||
)
|
||||
response, status = method(api, tenant_id, account, "template-1")
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert len(service_calls) == 1
|
||||
@@ -249,9 +242,7 @@ class TestCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module.RagPipelineService, "update_customized_pipeline_template", side_effect=update_template),
|
||||
):
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "template-1"
|
||||
)
|
||||
response, status = method(api, tenant_id, account, "template-1")
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert len(service_calls) == 1
|
||||
@@ -358,9 +349,7 @@ class TestPublishCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module, "RagPipelineService", Service),
|
||||
):
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "pipeline-1"
|
||||
)
|
||||
response, status = method(api, tenant_id, account, "pipeline-1")
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert service_calls == [("pipeline-1", payload, account, tenant_id)]
|
||||
@@ -397,9 +386,7 @@ class TestPublishCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module, "RagPipelineService", Service),
|
||||
):
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "pipeline-1"
|
||||
)
|
||||
response, status = method(api, tenant_id, account, "pipeline-1")
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert service_calls == [
|
||||
|
||||
@@ -15,7 +15,6 @@ from controllers.console.datasets.error import DatasetNameDuplicateError
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_datasets import (
|
||||
CreateEmptyRagPipelineDatasetApi,
|
||||
CreateRagPipelineDatasetApi,
|
||||
RagPipelineDatasetImportPayload,
|
||||
)
|
||||
from services.entities.dsl_entities import ImportStatus
|
||||
|
||||
@@ -51,7 +50,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
return_value=mock_service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
response, status = method(api, "tenant-1", user)
|
||||
|
||||
assert status == 201
|
||||
assert response == {
|
||||
@@ -76,7 +75,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, "tenant-1", user)
|
||||
|
||||
def test_post_dataset_name_duplicate(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -97,7 +96,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, "tenant-1", user)
|
||||
|
||||
def test_post_invalid_payload(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -111,7 +110,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
method(api, "tenant-1", user)
|
||||
|
||||
|
||||
class TestCreateEmptyRagPipelineDatasetApi:
|
||||
|
||||
@@ -8,14 +8,12 @@ from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.app.error import DraftWorkflowNotExist
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable import (
|
||||
PaginationQuery,
|
||||
RagPipelineEnvironmentVariableCollectionApi,
|
||||
RagPipelineNodeVariableCollectionApi,
|
||||
RagPipelineSystemVariableCollectionApi,
|
||||
RagPipelineVariableApi,
|
||||
RagPipelineVariableCollectionApi,
|
||||
RagPipelineVariableResetApi,
|
||||
WorkflowDraftVariablePatchPayload,
|
||||
)
|
||||
from core.workflow.llm_environment_variable import LLMEnvironmentVariable
|
||||
from core.workflow.variable_prefixes import SYSTEM_VARIABLE_NODE_ID
|
||||
@@ -74,7 +72,7 @@ class TestRagPipelineVariableCollectionApi:
|
||||
return_value=draft_srv,
|
||||
),
|
||||
):
|
||||
result = method(api, PaginationQuery(page=1, limit=10), editor_user, pipeline)
|
||||
result = method(api, editor_user, pipeline)
|
||||
|
||||
assert result is var_list
|
||||
draft_srv.list_variables_without_values.assert_called_once_with(
|
||||
@@ -102,7 +100,7 @@ class TestRagPipelineVariableCollectionApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotExist):
|
||||
method(api, PaginationQuery(), editor_user, pipeline)
|
||||
method(api, editor_user, pipeline)
|
||||
|
||||
def test_delete_variables_success(self, app: Flask, fake_db, editor_user):
|
||||
api = RagPipelineVariableCollectionApi()
|
||||
@@ -200,7 +198,7 @@ class TestRagPipelineVariableApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
method(api, WorkflowDraftVariablePatchPayload.model_validate(payload), editor_user, pipeline, "v1")
|
||||
method(api, editor_user, pipeline, "v1")
|
||||
|
||||
def test_delete_variable_success(self, app: Flask, fake_db, editor_user):
|
||||
api = RagPipelineVariableApi()
|
||||
|
||||
@@ -11,12 +11,10 @@ from flask import Flask
|
||||
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_import import (
|
||||
IncludeSecretQuery,
|
||||
RagPipelineExportApi,
|
||||
RagPipelineImportApi,
|
||||
RagPipelineImportCheckDependenciesApi,
|
||||
RagPipelineImportConfirmApi,
|
||||
RagPipelineImportPayload,
|
||||
)
|
||||
from core.plugin.entities.plugin import PluginDependency, PluginDependencyType
|
||||
from models.dataset import Pipeline
|
||||
@@ -69,7 +67,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
response, status = method(api, user)
|
||||
|
||||
assert status == 200
|
||||
assert response == {
|
||||
@@ -107,7 +105,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
response, status = method(api, user)
|
||||
|
||||
assert status == 400
|
||||
assert response["status"] == "failed"
|
||||
@@ -141,7 +139,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
response, status = method(api, user)
|
||||
|
||||
assert status == 202
|
||||
assert response["status"] == "pending"
|
||||
@@ -289,7 +287,7 @@ class TestRagPipelineExportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, IncludeSecretQuery(), pipeline)
|
||||
response, status = method(api, pipeline)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"data": "yaml: data"}
|
||||
|
||||
@@ -16,14 +16,6 @@ from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from controllers.console.datasets.rag_pipeline import rag_pipeline_workflow as module
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_workflow import (
|
||||
DraftWorkflowRunPayload,
|
||||
NodeIdQuery,
|
||||
PublishedWorkflowRunPayload,
|
||||
RagPipelineRecommendedPluginQuery,
|
||||
WorkflowListQuery,
|
||||
WorkflowUpdatePayload,
|
||||
)
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from models.account import Account, TenantAccountRole
|
||||
from models.dataset import Pipeline
|
||||
@@ -141,9 +133,7 @@ def test_published_rag_pipeline_workflows_serialize_items_before_session_closes(
|
||||
method="GET",
|
||||
query_string={"page": 1, "limit": 10, "user_id": "", "named_only": "false"},
|
||||
):
|
||||
response = handler(
|
||||
api, WorkflowListQuery(page=1, limit=10, user_id="", named_only=False), _account(), pipeline=pipeline
|
||||
)
|
||||
response = handler(api, _account(), pipeline=pipeline)
|
||||
|
||||
assert response["items"][0]["id"] == DEFAULT_WORKFLOW_ID
|
||||
assert response["page"] == 1
|
||||
@@ -167,7 +157,6 @@ def test_rag_pipeline_workflow_patch_serializes_response_model(
|
||||
):
|
||||
response = handler(
|
||||
api,
|
||||
WorkflowUpdatePayload.model_validate(payload),
|
||||
_account(),
|
||||
pipeline=_pipeline(),
|
||||
workflow_id=DEFAULT_WORKFLOW_ID,
|
||||
@@ -209,7 +198,7 @@ def test_draft_rag_pipeline_second_step_parameters_serializes_variables(database
|
||||
database_app.test_request_context("/?node_id=node-1"),
|
||||
patch.object(RagPipelineService, "get_second_step_parameters", return_value=variables),
|
||||
):
|
||||
response = handler(api, NodeIdQuery(node_id="node-1"), _pipeline())
|
||||
response = handler(api, _pipeline())
|
||||
|
||||
assert response["variables"] == variables
|
||||
|
||||
@@ -226,7 +215,7 @@ def test_rag_pipeline_recommended_plugins_serializes_known_envelope(database_app
|
||||
database_app.test_request_context("/?type=tool"),
|
||||
patch.object(RagPipelineService, "get_recommended_plugins", return_value=recommended_plugins),
|
||||
):
|
||||
response = handler(api, RagPipelineRecommendedPluginQuery(type="tool"), DEFAULT_WORKFLOW_TENANT_ID, _account())
|
||||
response = handler(api, DEFAULT_WORKFLOW_TENANT_ID, _account())
|
||||
|
||||
assert response == recommended_plugins
|
||||
|
||||
@@ -281,12 +270,7 @@ def test_rag_pipeline_run_uses_sqlite_session(
|
||||
patch.object(module.PipelineGenerateService, "generate", return_value=MagicMock()) as generate,
|
||||
patch.object(module.helper, "compact_generate_response", return_value={"ok": True}),
|
||||
):
|
||||
req_data = (
|
||||
DraftWorkflowRunPayload.model_validate(payload)
|
||||
if api_type is module.DraftRagPipelineRunApi
|
||||
else PublishedWorkflowRunPayload.model_validate(payload)
|
||||
)
|
||||
response = handler(api, req_data, session, _account(), pipeline.id)
|
||||
response = handler(api, session, _account(), pipeline.id)
|
||||
|
||||
assert response == {"ok": True}
|
||||
load_pipeline.assert_called_once_with(session, pipeline.id)
|
||||
@@ -309,11 +293,6 @@ def test_rag_pipeline_run_translates_rate_limit(
|
||||
api = api_type()
|
||||
handler = unwrap_all(api.post)
|
||||
pipeline = _pipeline()
|
||||
req_data = (
|
||||
DraftWorkflowRunPayload.model_validate(payload)
|
||||
if api_type is module.DraftRagPipelineRunApi
|
||||
else PublishedWorkflowRunPayload.model_validate(payload)
|
||||
)
|
||||
|
||||
with (
|
||||
Session(sqlite_engine) as session,
|
||||
@@ -322,4 +301,4 @@ def test_rag_pipeline_run_translates_rate_limit(
|
||||
patch.object(module.PipelineGenerateService, "generate", side_effect=InvokeRateLimitError("limit")),
|
||||
pytest.raises(InvokeRateLimitHttpError),
|
||||
):
|
||||
handler(api, req_data, session, _account(), pipeline.id)
|
||||
handler(api, session, _account(), pipeline.id)
|
||||
|
||||
@@ -22,12 +22,8 @@ import services
|
||||
from controllers.console.app.error import DraftWorkflowNotExist, DraftWorkflowNotSync
|
||||
from controllers.console.datasets.rag_pipeline import rag_pipeline_workflow as workflow_controller
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_workflow import (
|
||||
DatasourceVariablesPayload,
|
||||
DefaultBlockConfigQuery,
|
||||
DefaultRagPipelineBlockConfigApi,
|
||||
DraftRagPipelineApi,
|
||||
NodeRunPayload,
|
||||
NodeRunRequiredPayload,
|
||||
PublishedAllRagPipelineApi,
|
||||
PublishedRagPipelineApi,
|
||||
RagPipelineByIdApi,
|
||||
@@ -37,12 +33,9 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_workflow import (
|
||||
RagPipelineDraftRunLoopNodeApi,
|
||||
RagPipelineDraftWorkflowRestoreApi,
|
||||
RagPipelineRecommendedPluginApi,
|
||||
RagPipelineRecommendedPluginQuery,
|
||||
RagPipelineTaskStopApi,
|
||||
RagPipelineWorkflowLastRunApi,
|
||||
RagPipelineWorkflowRunNodeExecutionListApi,
|
||||
WorkflowListQuery,
|
||||
WorkflowUpdatePayload,
|
||||
)
|
||||
from graphon.enums import WorkflowNodeExecutionStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
@@ -425,7 +418,7 @@ class TestDraftRunNodes:
|
||||
return_value={"ok": True},
|
||||
),
|
||||
):
|
||||
result = method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
result = method(api, user, pipeline, "node")
|
||||
assert result == {"ok": True}
|
||||
|
||||
def test_iteration_node_conversation_not_exists(self, app: Flask) -> None:
|
||||
@@ -443,7 +436,7 @@ class TestDraftRunNodes:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
method(api, user, pipeline, "node")
|
||||
|
||||
def test_loop_node_success(self, app: Flask) -> None:
|
||||
api = RagPipelineDraftRunLoopNodeApi()
|
||||
@@ -463,7 +456,7 @@ class TestDraftRunNodes:
|
||||
return_value={"ok": True},
|
||||
),
|
||||
):
|
||||
assert method(api, NodeRunPayload(), user, pipeline, "node") == {"ok": True}
|
||||
assert method(api, user, pipeline, "node") == {"ok": True}
|
||||
|
||||
|
||||
class TestDraftNodeRun:
|
||||
@@ -485,7 +478,7 @@ class TestDraftNodeRun:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, NodeRunRequiredPayload(inputs={}), user, pipeline, "node")
|
||||
method(api, user, pipeline, "node")
|
||||
|
||||
|
||||
class TestPublishedPipelineApis:
|
||||
@@ -558,7 +551,7 @@ class TestMiscApis:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, RagPipelineRecommendedPluginQuery(type="all"), tenant_id, user)
|
||||
result = method(api, tenant_id, user)
|
||||
assert result == recommended_plugins
|
||||
service.get_recommended_plugins.assert_called_once_with("all", user, tenant_id)
|
||||
|
||||
@@ -580,7 +573,7 @@ class TestDefaultBlockConfigApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, DefaultBlockConfigQuery(q="{}"), pipeline, "llm")
|
||||
result = method(api, pipeline, "llm")
|
||||
assert result == {"k": "v"}
|
||||
|
||||
def test_get_block_config_invalid_json(self, app: Flask) -> None:
|
||||
@@ -591,7 +584,7 @@ class TestDefaultBlockConfigApi:
|
||||
|
||||
with app.test_request_context("/?q=bad-json"):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DefaultBlockConfigQuery(q="bad-json"), pipeline, "llm")
|
||||
method(api, pipeline, "llm")
|
||||
|
||||
|
||||
class TestPublishedAllRagPipelineApi:
|
||||
@@ -612,7 +605,7 @@ class TestPublishedAllRagPipelineApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, WorkflowListQuery(), user, pipeline)
|
||||
result = method(api, user, pipeline)
|
||||
|
||||
assert result["items"][0]["id"] == "w1"
|
||||
assert result["items"][0]["graph"] == {"nodes": [], "edges": []}
|
||||
@@ -629,7 +622,7 @@ class TestPublishedAllRagPipelineApi:
|
||||
app.test_request_context("/?user_id=u2"),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, WorkflowListQuery(user_id="u2"), user, pipeline)
|
||||
method(api, user, pipeline)
|
||||
|
||||
|
||||
class TestRagPipelineByIdApi:
|
||||
@@ -654,7 +647,7 @@ class TestRagPipelineByIdApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, WorkflowUpdatePayload.model_validate(payload), user, pipeline, "w1")
|
||||
result = method(api, user, pipeline, "w1")
|
||||
|
||||
assert result["id"] == "w1"
|
||||
assert result["marked_name"] == "test"
|
||||
@@ -668,7 +661,7 @@ class TestRagPipelineByIdApi:
|
||||
user = make_account()
|
||||
|
||||
with app.test_request_context("/", json={}):
|
||||
result, status = method(api, WorkflowUpdatePayload(), user, pipeline, "w1")
|
||||
result, status = method(api, user, pipeline, "w1")
|
||||
assert status == 400
|
||||
|
||||
def test_delete_success(self, app: Flask) -> None:
|
||||
@@ -804,6 +797,6 @@ class TestRagPipelineDatasourceVariableApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, DatasourceVariablesPayload.model_validate(payload), user, pipeline)
|
||||
result = method(api, user, pipeline)
|
||||
assert result["node_id"] == "n1"
|
||||
assert result["process_data"] == {}
|
||||
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.console.datasets import data_source as module
|
||||
from controllers.console.datasets.data_source import DataSourceApi, DataSourceNotionListApi, DataSourceNotionListQuery
|
||||
from controllers.console.datasets.data_source import DataSourceApi, DataSourceNotionListApi
|
||||
from models import Account, DataSourceOauthBinding
|
||||
from models.engine import db
|
||||
|
||||
@@ -217,11 +217,7 @@ def test_notion_pre_import_pages_serializes_frontend_list_shape(
|
||||
patch("core.datasource.datasource_manager.DatasourceManager.get_datasource_runtime", return_value=runtime),
|
||||
):
|
||||
response, status = unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1"),
|
||||
sqlite_session,
|
||||
"tenant-1",
|
||||
current_user,
|
||||
DataSourceNotionListApi(), sqlite_session, "tenant-1", current_user
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
@@ -259,13 +255,7 @@ def test_notion_pre_import_pages_rejects_missing_credential(
|
||||
patch.object(module.DatasourceProviderService, "get_datasource_credentials", return_value=None),
|
||||
pytest.raises(NotFound, match="Credential not found"),
|
||||
):
|
||||
unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1"),
|
||||
sqlite_session,
|
||||
TENANT_ID,
|
||||
current_user,
|
||||
)
|
||||
unwrap(DataSourceNotionListApi().get)(DataSourceNotionListApi(), sqlite_session, TENANT_ID, current_user)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
|
||||
@@ -286,10 +276,4 @@ def test_notion_pre_import_pages_rejects_non_notion_dataset(
|
||||
patch.object(module.DatasetService, "get_dataset", return_value=dataset),
|
||||
pytest.raises(ValueError, match="Dataset is not notion type"),
|
||||
):
|
||||
unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1", dataset_id="dataset-1"),
|
||||
sqlite_session,
|
||||
TENANT_ID,
|
||||
current_user,
|
||||
)
|
||||
unwrap(DataSourceNotionListApi().get)(DataSourceNotionListApi(), sqlite_session, TENANT_ID, current_user)
|
||||
|
||||
@@ -15,8 +15,6 @@ from controllers.console.datasets.data_source import (
|
||||
DataSourceNotionDocumentSyncApi,
|
||||
DataSourceNotionIndexingEstimateApi,
|
||||
DataSourceNotionPreviewApi,
|
||||
DataSourceNotionPreviewQuery,
|
||||
NotionEstimatePayload,
|
||||
)
|
||||
from core.rag.index_processor.constant.index_type import IndexStructureType
|
||||
from models import Account
|
||||
@@ -47,7 +45,7 @@ class TestDataSourceNotionPreviewApi:
|
||||
return_value=extractor,
|
||||
),
|
||||
):
|
||||
response, status = method(api, DataSourceNotionPreviewQuery(credential_id="c1"), "tenant-1", "p1", "page")
|
||||
response, status = method(api, "tenant-1", "p1", "page")
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -82,7 +80,7 @@ class TestDataSourceNotionIndexingEstimateApi:
|
||||
return_value=MagicMock(model_dump=lambda: {"total_pages": 1}),
|
||||
),
|
||||
):
|
||||
response, status = method(api, NotionEstimatePayload.model_validate(payload), sqlite_session, "tenant-1")
|
||||
response, status = method(api, sqlite_session, "tenant-1")
|
||||
|
||||
assert status == 200
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ from controllers.console.datasets.datasets import (
|
||||
DatasetApiDeleteApi,
|
||||
DatasetApiKeyApi,
|
||||
DatasetAutoDisableLogApi,
|
||||
DatasetCreatePayload,
|
||||
DatasetEnableApiApi,
|
||||
DatasetErrorDocs,
|
||||
DatasetIndexingEstimateApi,
|
||||
@@ -29,9 +28,7 @@ from controllers.console.datasets.datasets import (
|
||||
DatasetRelatedAppListApi,
|
||||
DatasetRetrievalSettingApi,
|
||||
DatasetRetrievalSettingMockApi,
|
||||
DatasetUpdatePayload,
|
||||
DatasetUseCheckApi,
|
||||
IndexingEstimatePayload,
|
||||
_get_retrieval_methods_by_vector_type,
|
||||
)
|
||||
from controllers.console.datasets.error import DatasetInUseError, DatasetNameDuplicateError, IndexingEstimateError
|
||||
@@ -492,7 +489,7 @@ class TestDatasetListApiPost:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
patch.object(DatasetService, "create_empty_dataset", return_value=dataset),
|
||||
):
|
||||
_, status = method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", user)
|
||||
_, status = method(api, MagicMock(), "tenant-1", user)
|
||||
assert status == 201
|
||||
|
||||
def test_post_forbidden(self, app: Flask):
|
||||
@@ -502,7 +499,7 @@ class TestDatasetListApiPost:
|
||||
user = make_account(TenantAccountRole.NORMAL)
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", user)
|
||||
method(api, MagicMock(), "tenant-1", user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -517,14 +514,14 @@ class TestDatasetListApiPost:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", user)
|
||||
method(api, MagicMock(), "tenant-1", user)
|
||||
|
||||
def test_post_invalid_payload_missing_name(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
method = unwrap(api.post)
|
||||
with app.test_request_context("/datasets", json={}), patch.object(type(console_ns), "payload", {}):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, DatasetCreatePayload(), MagicMock(), "tenant-1", make_account())
|
||||
method(api, MagicMock(), "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_indexing_technique(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -532,7 +529,7 @@ class TestDatasetListApiPost:
|
||||
payload = {"name": "bad", "indexing_technique": "invalid-tech"}
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(ValueError, match="Invalid indexing technique"):
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", make_account())
|
||||
method(api, MagicMock(), "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_provider(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -540,7 +537,7 @@ class TestDatasetListApiPost:
|
||||
payload = {"name": "bad", "provider": "unknown"}
|
||||
with app.test_request_context("/datasets", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(ValueError, match="Invalid provider"):
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", make_account())
|
||||
method(api, MagicMock(), "tenant-1", make_account())
|
||||
|
||||
|
||||
class TestDatasetApiGet:
|
||||
@@ -695,7 +692,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetService, "update_dataset", return_value=dataset),
|
||||
patch.object(DatasetPermissionService, "get_dataset_partial_member_list", return_value=[]),
|
||||
):
|
||||
result, status = method(api, DatasetUpdatePayload(), MagicMock(), tenant_id, user, dataset_id)
|
||||
result, status = method(api, MagicMock(), tenant_id, user, dataset_id)
|
||||
assert status == 200
|
||||
assert result["partial_member_list"] == []
|
||||
|
||||
@@ -707,7 +704,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, DatasetUpdatePayload(), MagicMock(), "tenant-1", make_account(), "missing")
|
||||
method(api, MagicMock(), "tenant-1", make_account(), "missing")
|
||||
|
||||
def test_patch_permission_denied(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -722,7 +719,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetPermissionService, "check_permission", side_effect=Forbidden("no permission")),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
method(api, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
|
||||
def test_patch_partial_members_update(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -739,7 +736,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetPermissionService, "update_partial_member_list", return_value=None),
|
||||
patch.object(DatasetPermissionService, "get_dataset_partial_member_list", return_value=["u1", "u2"]),
|
||||
):
|
||||
result, _ = method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
result, _ = method(api, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
assert result["partial_member_list"] == ["u1", "u2"]
|
||||
|
||||
def test_patch_clear_partial_members(self, app: Flask):
|
||||
@@ -757,7 +754,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetPermissionService, "clear_partial_member_list", return_value=None),
|
||||
patch.object(DatasetPermissionService, "get_dataset_partial_member_list", return_value=[]),
|
||||
):
|
||||
result, _ = method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
result, _ = method(api, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
assert result["partial_member_list"] == []
|
||||
|
||||
|
||||
@@ -1017,12 +1014,7 @@ class TestDatasetIndexingEstimateApi:
|
||||
patch("controllers.console.datasets.datasets.DocumentService.estimate_args_validate", return_value=None),
|
||||
patch("controllers.console.datasets.datasets.IndexingRunner.indexing_estimate", return_value=mock_response),
|
||||
):
|
||||
response, status = method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
response, status = method(api, session, "tenant-1")
|
||||
assert status == 200
|
||||
assert response == {
|
||||
"tokens": 0,
|
||||
@@ -1044,12 +1036,7 @@ class TestDatasetIndexingEstimateApi:
|
||||
patch("controllers.console.datasets.datasets.DocumentService.estimate_args_validate", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(api, session, "tenant-1")
|
||||
|
||||
def test_post_llm_bad_request_error(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1068,12 +1055,7 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(api, session, "tenant-1")
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1092,12 +1074,7 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(api, session, "tenant-1")
|
||||
|
||||
def test_post_generic_exception(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1115,12 +1092,7 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(IndexingEstimateError):
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
method(api, session, "tenant-1")
|
||||
|
||||
|
||||
class TestDatasetRelatedAppListApi:
|
||||
|
||||
@@ -9,11 +9,9 @@ from flask import Flask
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
import services
|
||||
from controllers.common.controller_schemas import ChildChunkCreatePayload, ChildChunkUpdatePayload
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.app.error import ProviderNotInitializeError
|
||||
from controllers.console.datasets.datasets_segments import (
|
||||
BatchImportPayload,
|
||||
ChildChunkAddApi,
|
||||
ChildChunkBatchUpdatePayload,
|
||||
ChildChunkUpdateApi,
|
||||
@@ -22,8 +20,6 @@ from controllers.console.datasets.datasets_segments import (
|
||||
DatasetDocumentSegmentBatchImportApi,
|
||||
DatasetDocumentSegmentListApi,
|
||||
DatasetDocumentSegmentUpdateApi,
|
||||
SegmentCreatePayload,
|
||||
SegmentUpdatePayload,
|
||||
)
|
||||
from controllers.console.datasets.error import ChildChunkDeleteIndexError, ChildChunkIndexingError, InvalidActionError
|
||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
@@ -54,7 +50,6 @@ def _segment():
|
||||
status=SegmentStatus.COMPLETED,
|
||||
updated_by="u1",
|
||||
)
|
||||
|
||||
segment.id = "seg-1"
|
||||
segment.created_at = naive_utc_now()
|
||||
segment.updated_at = naive_utc_now()
|
||||
@@ -359,9 +354,7 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, SegmentCreatePayload(content="test content"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
assert status == 200
|
||||
assert response["data"]["id"] == "seg-1"
|
||||
|
||||
@@ -385,9 +378,7 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), MagicMock(), "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetDocumentSegmentAddApi()
|
||||
@@ -409,9 +400,7 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), MagicMock(), "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentUpdateApi:
|
||||
@@ -452,9 +441,7 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, SegmentUpdatePayload(content="test content"), session, "tenant-1", user, "ds-1", "doc-1", "seg-1"
|
||||
)
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
assert status == 200
|
||||
assert "data" in response
|
||||
|
||||
@@ -480,16 +467,7 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
|
||||
def test_patch_segment_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -517,16 +495,7 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
|
||||
def test_patch_llm_bad_request(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -556,16 +525,7 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentBatchImportApi:
|
||||
@@ -604,9 +564,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
assert status == 200
|
||||
assert response["job_status"] == "waiting"
|
||||
|
||||
@@ -623,15 +581,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
patch("controllers.console.datasets.datasets_segments.DatasetService.get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_document_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -649,15 +599,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_upload_file_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -677,9 +619,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_invalid_file_type(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -701,9 +641,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_post_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -727,9 +665,7 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
"controllers.console.datasets.datasets_segments.redis_client.setnx", side_effect=Exception("redis down")
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
assert status == 500
|
||||
assert "error" in response
|
||||
|
||||
@@ -811,9 +747,7 @@ class TestChildChunkAddApi:
|
||||
return_value=child_chunk,
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, ChildChunkCreatePayload(content="child"), MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1"
|
||||
)
|
||||
response, status = method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
assert status == 200
|
||||
assert response["data"]["id"] == "cc-1"
|
||||
|
||||
@@ -844,16 +778,7 @@ class TestChildChunkAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ChildChunkIndexingError):
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
|
||||
def test_post_permission_denied(self, app: Flask):
|
||||
api = ChildChunkAddApi()
|
||||
@@ -874,16 +799,7 @@ class TestChildChunkAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
|
||||
|
||||
class TestChildChunkUpdateApi:
|
||||
@@ -1006,17 +922,7 @@ class TestChildChunkUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
ChildChunkUpdatePayload(content="updated child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
"cc-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
|
||||
|
||||
class TestSegmentListAdvancedCases:
|
||||
@@ -1141,9 +1047,7 @@ class TestSegmentOperationCases:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderTokenNotInitError):
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), MagicMock(), "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_batch_import_with_document_not_found(self, app: Flask):
|
||||
"""Test batch import with document not found"""
|
||||
@@ -1159,15 +1063,7 @@ class TestSegmentOperationCases:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_batch_import_with_invalid_file(self, app: Flask):
|
||||
"""Test batch import with invalid file type"""
|
||||
@@ -1187,9 +1083,7 @@ class TestSegmentOperationCases:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=document),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
|
||||
def test_batch_import_with_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -1228,9 +1122,7 @@ class TestSegmentOperationCases:
|
||||
side_effect=Exception("Task failed"),
|
||||
),
|
||||
):
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
assert status == 500
|
||||
assert "error" in response
|
||||
|
||||
|
||||
@@ -12,14 +12,10 @@ from controllers.console import console_ns
|
||||
from controllers.console.datasets.error import DatasetNameDuplicateError
|
||||
from controllers.console.datasets.external import (
|
||||
BedrockRetrievalApi,
|
||||
BedrockRetrievalPayload,
|
||||
ExternalApiTemplateApi,
|
||||
ExternalApiTemplateListApi,
|
||||
ExternalApiTemplateListQuery,
|
||||
ExternalApiUseCheckApi,
|
||||
ExternalDatasetCreateApi,
|
||||
ExternalHitTestingPayload,
|
||||
ExternalKnowledgeApiPayload,
|
||||
ExternalKnowledgeHitTestingApi,
|
||||
)
|
||||
from models.account import Account, TenantAccountRole
|
||||
@@ -178,9 +174,7 @@ class TestExternalApiTemplateListApi:
|
||||
return_value=([api_item], 3),
|
||||
) as get_external_knowledge_apis,
|
||||
):
|
||||
resp, status = method(
|
||||
api, ExternalApiTemplateListQuery(page=2, limit=1, keyword="vector"), session, "tenant-1"
|
||||
)
|
||||
resp, status = method(api, session, "tenant-1")
|
||||
|
||||
assert status == 200
|
||||
assert resp == {
|
||||
@@ -219,9 +213,7 @@ class TestExternalApiTemplateListApi:
|
||||
return_value=created,
|
||||
) as create_external_knowledge_api,
|
||||
):
|
||||
resp, status = method(
|
||||
api, ExternalKnowledgeApiPayload.model_validate(payload), session, "tenant-1", current_user
|
||||
)
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
|
||||
assert status == 201
|
||||
assert resp == _external_api_dict("api-created")
|
||||
@@ -247,7 +239,7 @@ class TestExternalApiTemplateListApi:
|
||||
patch.object(ExternalDatasetService, "validate_api_list"),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask, current_user: Account):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -266,7 +258,7 @@ class TestExternalApiTemplateListApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalApiTemplateApi:
|
||||
@@ -333,14 +325,7 @@ class TestExternalApiTemplateApi:
|
||||
return_value=updated,
|
||||
) as update_external_knowledge_api,
|
||||
):
|
||||
resp, status = method(
|
||||
api,
|
||||
ExternalKnowledgeApiPayload.model_validate(payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
current_user,
|
||||
"api-updated",
|
||||
)
|
||||
resp, status = method(api, session, "tenant-1", current_user, "api-updated")
|
||||
|
||||
assert status == 200
|
||||
assert resp == _external_api_dict("api-updated")
|
||||
@@ -420,9 +405,7 @@ class TestExternalDatasetCreateApi:
|
||||
) as dataset_response_source,
|
||||
):
|
||||
session = MagicMock()
|
||||
resp, status = method(
|
||||
api, ExternalDatasetCreatePayload.model_validate(payload), session, "tenant-1", current_user
|
||||
)
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
|
||||
assert status == 201
|
||||
assert resp == _expected_dataset_detail_payload()
|
||||
@@ -450,7 +433,7 @@ class TestExternalDatasetCreateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApi:
|
||||
@@ -467,7 +450,7 @@ class TestExternalKnowledgeHitTestingApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, ExternalHitTestingPayload(query="test"), MagicMock(), current_user, "dataset-id")
|
||||
method(api, MagicMock(), current_user, "dataset-id")
|
||||
|
||||
def test_hit_testing_success(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
@@ -513,7 +496,7 @@ class TestExternalKnowledgeHitTestingApi:
|
||||
) as external_retrieve,
|
||||
patch("controllers.console.datasets.external.dump_response", side_effect=lambda _model, value: value),
|
||||
):
|
||||
resp = method(api, ExternalHitTestingPayload.model_validate(payload), session, current_user, "dataset-id")
|
||||
resp = method(api, session, current_user, "dataset-id")
|
||||
|
||||
assert resp == retrieve_response
|
||||
check_dataset_permission.assert_called_once_with(dataset, current_user, session)
|
||||
@@ -566,7 +549,7 @@ class TestBedrockRetrievalApi:
|
||||
return_value=retrieval_response,
|
||||
) as knowledge_retrieval,
|
||||
):
|
||||
resp, status = method(api, BedrockRetrievalPayload.model_validate(payload))
|
||||
resp, status = method()
|
||||
|
||||
assert status == 200
|
||||
assert resp == retrieval_response
|
||||
@@ -593,7 +576,7 @@ class TestExternalApiTemplateListApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
|
||||
def test_get_with_pagination(self, app: Flask):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -608,7 +591,7 @@ class TestExternalApiTemplateListApiAdvanced:
|
||||
return_value=(templates, 25),
|
||||
) as get_external_knowledge_apis,
|
||||
):
|
||||
resp, status = method(api, ExternalApiTemplateListQuery(page=2, limit=3), MagicMock(), "tenant-1")
|
||||
resp, status = method(api, MagicMock(), "tenant-1")
|
||||
|
||||
assert status == 200
|
||||
assert resp == {
|
||||
@@ -638,7 +621,7 @@ class TestExternalDatasetCreateApiAdvanced:
|
||||
|
||||
with app.test_request_context("/", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
@@ -661,7 +644,7 @@ class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, ExternalHitTestingPayload.model_validate(payload), MagicMock(), current_user, "ds-1")
|
||||
method(api, MagicMock(), current_user, "ds-1")
|
||||
|
||||
def test_hit_testing_with_custom_retrieval_model(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
@@ -699,7 +682,7 @@ class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
},
|
||||
) as external_retrieve,
|
||||
):
|
||||
resp = method(api, ExternalHitTestingPayload.model_validate(payload), session, current_user, "ds-1")
|
||||
resp = method(api, session, current_user, "ds-1")
|
||||
|
||||
assert resp == {
|
||||
"query": {"content": "test query"},
|
||||
@@ -744,4 +727,4 @@ class TestBedrockRetrievalApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, BedrockRetrievalPayload.model_validate(payload))
|
||||
method()
|
||||
|
||||
@@ -7,7 +7,6 @@ from flask import Flask
|
||||
from pytest_mock import MockerFixture
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.common.controller_schemas import MetadataUpdatePayload
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.datasets.metadata import (
|
||||
DatasetMetadataApi,
|
||||
@@ -25,7 +24,6 @@ from services.metadata_service import MetadataService
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask("test_dataset_metadata")
|
||||
|
||||
app.config["TESTING"] = True
|
||||
return app
|
||||
|
||||
@@ -78,9 +76,7 @@ class TestDatasetMetadataCreateApi:
|
||||
MetadataService, "create_metadata", return_value={"id": "m1", "type": "string", "name": "author"}
|
||||
),
|
||||
):
|
||||
result, status = method(
|
||||
api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id
|
||||
)
|
||||
result, status = method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
assert status == 201
|
||||
assert result["type"] == "string"
|
||||
assert result["name"] == "author"
|
||||
@@ -96,9 +92,7 @@ class TestDatasetMetadataCreateApi:
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(
|
||||
api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id
|
||||
)
|
||||
method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
|
||||
|
||||
class TestDatasetMetadataGetApi:
|
||||
@@ -146,15 +140,7 @@ class TestDatasetMetadataApi:
|
||||
return_value={"id": "m1", "type": "string", "name": "updated-name"},
|
||||
),
|
||||
):
|
||||
result, status = method(
|
||||
api,
|
||||
MetadataUpdatePayload(name="updated-name"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
current_user,
|
||||
dataset_id,
|
||||
metadata_id,
|
||||
)
|
||||
result, status = method(api, MagicMock(), "tenant-1", current_user, dataset_id, metadata_id)
|
||||
assert status == 200
|
||||
assert result["type"] == "string"
|
||||
assert result["name"] == "updated-name"
|
||||
@@ -215,14 +201,9 @@ class TestDocumentMetadataEditApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(DatasetService, "get_dataset", return_value=dataset),
|
||||
patch.object(DatasetService, "check_dataset_permission"),
|
||||
patch.object(MetadataOperationData, "model_validate", return_value=MagicMock()),
|
||||
patch.object(MetadataService, "update_documents_metadata"),
|
||||
):
|
||||
result, status = method(
|
||||
api,
|
||||
MetadataOperationData(operation_data=[{"document_id": "doc-1", "metadata_list": []}]),
|
||||
MagicMock(),
|
||||
current_user,
|
||||
dataset_id,
|
||||
)
|
||||
result, status = method(api, MagicMock(), current_user, dataset_id)
|
||||
assert status == 204
|
||||
assert result == ""
|
||||
|
||||
@@ -16,7 +16,6 @@ from controllers.console.auth.error import (
|
||||
from controllers.console.error import AccountInFreezeError
|
||||
from controllers.console.workspace.account import (
|
||||
AccountAvatarApi,
|
||||
AccountAvatarQuery,
|
||||
AccountDeleteApi,
|
||||
AccountDeleteVerifyApi,
|
||||
AccountInitApi,
|
||||
@@ -217,7 +216,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/example",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
result = method(api, user)
|
||||
|
||||
assert result == {"avatar_url": "https://signed/example"}
|
||||
sign_mock.assert_called_once_with(upload_file_id=file_id)
|
||||
@@ -257,7 +256,7 @@ class TestAccountAvatarApiGet:
|
||||
) as sign_mock,
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
method(api, user)
|
||||
|
||||
sign_mock.assert_not_called()
|
||||
|
||||
@@ -290,7 +289,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/example",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
result = method(api, user)
|
||||
|
||||
assert result == {"avatar_url": "https://signed/example"}
|
||||
sign_mock.assert_called_once_with(upload_file_id=file_id)
|
||||
@@ -309,7 +308,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/should-not-use",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, AccountAvatarQuery(avatar=external), user)
|
||||
result = method(api, user)
|
||||
|
||||
assert result == {"avatar_url": external}
|
||||
sign_mock.assert_not_called()
|
||||
|
||||
@@ -17,9 +17,7 @@ from controllers.console.workspace.endpoint import (
|
||||
EndpointIdPayload,
|
||||
EndpointItemApi,
|
||||
EndpointListApi,
|
||||
EndpointListForPluginQuery,
|
||||
EndpointListForSinglePluginApi,
|
||||
EndpointListQuery,
|
||||
EndpointUpdatePayload,
|
||||
LegacyEndpointUpdatePayload,
|
||||
)
|
||||
@@ -148,7 +146,7 @@ class TestEndpointListApi:
|
||||
return_value=[endpoint_entity],
|
||||
),
|
||||
):
|
||||
result = method(api, EndpointListQuery(page=1, page_size=10), "t1", "u1")
|
||||
result = method(api, "t1", "u1")
|
||||
|
||||
endpoint = result["endpoints"][0]
|
||||
assert endpoint["id"] == "e1"
|
||||
@@ -182,7 +180,7 @@ class TestEndpointListApi:
|
||||
app.test_request_context("/?page=0&page_size=10"),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, EndpointListQuery(page=0, page_size=10), "t1", "u1")
|
||||
method(api, "t1", "u1")
|
||||
|
||||
|
||||
class TestEndpointListForSinglePluginApi:
|
||||
@@ -197,7 +195,7 @@ class TestEndpointListForSinglePluginApi:
|
||||
return_value=[_endpoint_entity()],
|
||||
),
|
||||
):
|
||||
result = method(api, EndpointListForPluginQuery(page=1, page_size=10, plugin_id="p1"), "t1", "u1")
|
||||
result = method(api, "t1", "u1")
|
||||
|
||||
assert result["endpoints"][0]["id"] == "e1"
|
||||
assert result["endpoints"][0]["settings"]["api_key"] == "pl********et"
|
||||
@@ -211,7 +209,7 @@ class TestEndpointListForSinglePluginApi:
|
||||
app.test_request_context("/?page=1&page_size=10"),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, EndpointListForPluginQuery(page=1, page_size=10), "t1", "u1")
|
||||
method(api, "t1", "u1")
|
||||
|
||||
|
||||
class TestEndpointItemApi:
|
||||
|
||||
@@ -15,16 +15,6 @@ from controllers.console.workspace.models import (
|
||||
ModelProviderModelEnableApi,
|
||||
ModelProviderModelParameterRuleApi,
|
||||
ModelProviderModelValidateApi,
|
||||
ParserCreateCredential,
|
||||
ParserDeleteCredential,
|
||||
ParserDeleteModels,
|
||||
ParserGetCredentials,
|
||||
ParserGetDefault,
|
||||
ParserParameter,
|
||||
ParserPostDefault,
|
||||
ParserPostModels,
|
||||
ParserSwitch,
|
||||
ParserValidate,
|
||||
)
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
from graphon.model_runtime.errors.validate import CredentialsValidateFailedError
|
||||
@@ -53,7 +43,7 @@ class TestDefaultModelApi:
|
||||
},
|
||||
}
|
||||
|
||||
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "tenant1")
|
||||
result = method(api, "tenant1")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -75,7 +65,7 @@ class TestDefaultModelApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, ParserPostDefault.model_validate(payload), "tenant1")
|
||||
result = method(api, "tenant1")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -89,7 +79,7 @@ class TestDefaultModelApi:
|
||||
):
|
||||
service.return_value.get_default_model_of_model_type.return_value = None
|
||||
|
||||
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "t1")
|
||||
result = method(api, "t1")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -127,7 +117,7 @@ class TestModelProviderModelApi:
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
patch("controllers.console.workspace.models.ModelLoadBalancingService"),
|
||||
):
|
||||
result, status = method(api, ParserPostModels.model_validate(payload), "tenant1", "openai")
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -144,7 +134,7 @@ class TestModelProviderModelApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
|
||||
assert status == 204
|
||||
|
||||
@@ -187,13 +177,7 @@ class TestModelProviderModelCredentialApi:
|
||||
provider_service.return_value.provider_manager.get_provider_model_available_credentials.return_value = []
|
||||
lb_service.return_value.get_load_balancing_configs.return_value = (False, [])
|
||||
|
||||
result = method(
|
||||
api,
|
||||
ParserGetCredentials(model="gpt-4", model_type=ModelType.LLM),
|
||||
"tenant1",
|
||||
SimpleNamespace(id="u1"),
|
||||
"openai",
|
||||
)
|
||||
result = method(api, "tenant1", SimpleNamespace(id="u1"), "openai")
|
||||
|
||||
assert "credentials" in result
|
||||
|
||||
@@ -211,7 +195,7 @@ class TestModelProviderModelCredentialApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, ParserCreateCredential.model_validate(payload), "tenant1", "openai")
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
|
||||
assert status == 201
|
||||
|
||||
@@ -228,13 +212,7 @@ class TestModelProviderModelCredentialApi:
|
||||
service.return_value.provider_manager.get_provider_model_available_credentials.return_value = []
|
||||
lb.return_value.get_load_balancing_configs.return_value = (False, [])
|
||||
|
||||
result = method(
|
||||
api,
|
||||
ParserGetCredentials(model="gpt", model_type=ModelType.LLM),
|
||||
"t1",
|
||||
SimpleNamespace(id="u1"),
|
||||
"openai",
|
||||
)
|
||||
result = method(api, "t1", SimpleNamespace(id="u1"), "openai")
|
||||
|
||||
assert result["credentials"] == {}
|
||||
|
||||
@@ -252,7 +230,7 @@ class TestModelProviderModelCredentialApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, ParserDeleteCredential.model_validate(payload), "t1", "openai")
|
||||
result, status = method(api, "t1", "openai")
|
||||
|
||||
assert status == 204
|
||||
|
||||
@@ -272,7 +250,7 @@ class TestModelProviderModelCredentialSwitchApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, ParserSwitch.model_validate(payload), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -291,7 +269,7 @@ class TestModelEnableDisableApis:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -308,7 +286,7 @@ class TestModelEnableDisableApis:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -328,7 +306,7 @@ class TestModelProviderModelValidateApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -349,7 +327,7 @@ class TestModelProviderModelValidateApi:
|
||||
):
|
||||
service_mock.return_value.validate_model_credentials.side_effect = CredentialsValidateFailedError("invalid")
|
||||
|
||||
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "error"
|
||||
|
||||
@@ -365,7 +343,7 @@ class TestParameterAndAvailableModels:
|
||||
):
|
||||
service_mock.return_value.get_model_parameter_rules.return_value = []
|
||||
|
||||
result = method(api, ParserParameter(model="gpt-4"), "tenant1", "openai")
|
||||
result = method(api, "tenant1", "openai")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -393,7 +371,7 @@ class TestParameterAndAvailableModels:
|
||||
):
|
||||
service.return_value.get_model_parameter_rules.return_value = []
|
||||
|
||||
result = method(api, ParserParameter(model="gpt"), "t1", "openai")
|
||||
result = method(api, "t1", "openai")
|
||||
|
||||
assert result["data"] == []
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.console.workspace import rbac as rbac_mod
|
||||
from controllers.console.workspace.rbac import _RolesListQuery
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -176,10 +175,7 @@ class TestPaginationMapping:
|
||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list,
|
||||
):
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"page": 1, "limit": 2, "include_owner": 1}),
|
||||
)
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
|
||||
|
||||
owner_permission_keys = rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["owner"]
|
||||
valid_owner_permission_keys = []
|
||||
@@ -234,7 +230,7 @@ class TestPaginationMapping:
|
||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
|
||||
):
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" not in names
|
||||
@@ -246,10 +242,7 @@ class TestPaginationMapping:
|
||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
|
||||
):
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"include_owner": 1}),
|
||||
)
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" in names
|
||||
@@ -261,7 +254,7 @@ class TestPaginationMapping:
|
||||
patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")),
|
||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"),
|
||||
):
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" not in names
|
||||
@@ -274,10 +267,7 @@ class TestPaginationMapping:
|
||||
patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list,
|
||||
patch("controllers.console.workspace.rbac._dump", return_value={}),
|
||||
):
|
||||
inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"page": 2, "limit": 50, "reverse": True, "include_owner": 1}),
|
||||
)
|
||||
inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi())
|
||||
|
||||
_, kwargs = mock_list.call_args
|
||||
options = kwargs["options"]
|
||||
|
||||
@@ -15,19 +15,15 @@ from controllers.console.workspace.trigger_providers import (
|
||||
TriggerOAuthAuthorizeApi,
|
||||
TriggerOAuthCallbackApi,
|
||||
TriggerOAuthClientManageApi,
|
||||
TriggerOAuthClientPayload,
|
||||
TriggerProviderIconApi,
|
||||
TriggerProviderInfoApi,
|
||||
TriggerProviderListApi,
|
||||
TriggerSubscriptionBuilderBuildApi,
|
||||
TriggerSubscriptionBuilderCreateApi,
|
||||
TriggerSubscriptionBuilderCreatePayload,
|
||||
TriggerSubscriptionBuilderGetApi,
|
||||
TriggerSubscriptionBuilderLogsApi,
|
||||
TriggerSubscriptionBuilderUpdateApi,
|
||||
TriggerSubscriptionBuilderUpdatePayload,
|
||||
TriggerSubscriptionBuilderVerifyApi,
|
||||
TriggerSubscriptionBuilderVerifyPayload,
|
||||
TriggerSubscriptionListApi,
|
||||
TriggerSubscriptionUpdateApi,
|
||||
TriggerSubscriptionVerifyApi,
|
||||
@@ -167,13 +163,7 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=subscription_builder(),
|
||||
),
|
||||
):
|
||||
result = method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderCreatePayload(credential_type="UNAUTHORIZED"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
)
|
||||
result = method(api, "t1", mock_user(), "github")
|
||||
assert result["subscription_builder"]["id"] == "b1"
|
||||
|
||||
def test_get_builder(self, app: Flask) -> None:
|
||||
@@ -206,14 +196,7 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value={"verified": True},
|
||||
),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={"a": 1}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
) == {"verified": True}
|
||||
assert method(api, "t1", mock_user(), "github", "b1") == {"verified": True}
|
||||
|
||||
def test_verify_builder_error(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionBuilderVerifyApi()
|
||||
@@ -227,14 +210,7 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
)
|
||||
method(api, "t1", mock_user(), "github", "b1")
|
||||
|
||||
def test_update_builder(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionBuilderUpdateApi()
|
||||
@@ -247,17 +223,7 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=subscription_builder(),
|
||||
) as mock_update_builder,
|
||||
):
|
||||
assert (
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="n"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
)["id"]
|
||||
== "b1"
|
||||
)
|
||||
assert method(api, "t1", mock_user(), "github", "b1")["id"] == "b1"
|
||||
mock_update_builder.assert_called_once_with(
|
||||
tenant_id="t1",
|
||||
user_id="u1",
|
||||
@@ -297,14 +263,7 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="x"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
) == {"result": "success"}
|
||||
assert method(api, "t1", mock_user(), "github", "b1") == {"result": "success"}
|
||||
|
||||
|
||||
class TestTriggerSubscriptionCrud:
|
||||
@@ -324,12 +283,7 @@ class TestTriggerSubscriptionCrud:
|
||||
),
|
||||
patch("controllers.console.workspace.trigger_providers.TriggerProviderService.update_trigger_subscription"),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="x"),
|
||||
"t1",
|
||||
"s1",
|
||||
) == {"result": "success"}
|
||||
assert method(api, "t1", "s1") == {"result": "success"}
|
||||
|
||||
def test_update_not_found(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionUpdateApi()
|
||||
@@ -343,7 +297,7 @@ class TestTriggerSubscriptionCrud:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFoundError):
|
||||
method(api, TriggerSubscriptionBuilderUpdatePayload(name="x"), "t1", "x")
|
||||
method(api, "t1", "x")
|
||||
|
||||
def test_update_rebuild(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionUpdateApi()
|
||||
@@ -365,12 +319,7 @@ class TestTriggerSubscriptionCrud:
|
||||
"controllers.console.workspace.trigger_providers.TriggerProviderService.rebuild_trigger_subscription"
|
||||
),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(credentials={}),
|
||||
"t1",
|
||||
"s1",
|
||||
) == {"result": "success"}
|
||||
assert method(api, "t1", "s1") == {"result": "success"}
|
||||
|
||||
|
||||
class TestTriggerOAuthApis:
|
||||
@@ -550,12 +499,7 @@ class TestTriggerOAuthClientManageApi:
|
||||
return_value={"result": "success"},
|
||||
),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerOAuthClientPayload(enabled=True),
|
||||
"t1",
|
||||
"github",
|
||||
) == {"result": "success"}
|
||||
assert method(api, "t1", "github") == {"result": "success"}
|
||||
|
||||
def test_delete_client(self, app: Flask) -> None:
|
||||
api = TriggerOAuthClientManageApi()
|
||||
@@ -582,7 +526,7 @@ class TestTriggerOAuthClientManageApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(api, TriggerOAuthClientPayload(enabled=True), "t1", "github")
|
||||
method(api, "t1", "github")
|
||||
|
||||
|
||||
class TestTriggerSubscriptionVerifyApi:
|
||||
@@ -597,14 +541,7 @@ class TestTriggerSubscriptionVerifyApi:
|
||||
return_value={"verified": True},
|
||||
),
|
||||
):
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"s1",
|
||||
) == {"verified": True}
|
||||
assert method(api, "t1", mock_user(), "github", "s1") == {"verified": True}
|
||||
|
||||
@pytest.mark.parametrize("raised_exception", [ValueError("bad"), Exception("boom")])
|
||||
def test_verify_errors(self, app: Flask, raised_exception: Exception) -> None:
|
||||
@@ -619,11 +556,4 @@ class TestTriggerSubscriptionVerifyApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"s1",
|
||||
)
|
||||
method(api, "t1", mock_user(), "github", "s1")
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from models.agent_config_entities import (
|
||||
AgentSoulModelSettings,
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
@@ -10,22 +9,6 @@ from models.agent_config_entities import (
|
||||
)
|
||||
|
||||
|
||||
def test_agent_soul_model_settings_preserves_plugin_declared_parameters() -> None:
|
||||
settings = AgentSoulModelSettings.model_validate(
|
||||
{
|
||||
"temperature": 0.7,
|
||||
"enable_thinking": True,
|
||||
"thinking_budget": 4096,
|
||||
}
|
||||
)
|
||||
|
||||
dumped = settings.model_dump(mode="json", exclude_none=True)
|
||||
|
||||
assert dumped["temperature"] == 0.7
|
||||
assert dumped["enable_thinking"] is True
|
||||
assert dumped["thinking_budget"] == 4096
|
||||
|
||||
|
||||
def test_file_default_value_accepts_canonical_reference_mapping() -> None:
|
||||
reference = build_file_reference(record_id="tool-file-1")
|
||||
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx": {
|
||||
"jsx_a11y/tabindex-no-positive": {
|
||||
"count": 3
|
||||
|
||||
@@ -1586,7 +1586,6 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -2037,13 +2037,6 @@ export const zAgentModelResponseFormatConfig = z.object({
|
||||
|
||||
/**
|
||||
* AgentSoulModelSettings
|
||||
*
|
||||
* Model parameters for the Agent Soul model.
|
||||
*
|
||||
* Model plugins can declare arbitrary parameters via ``parameter_rules``
|
||||
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
|
||||
* fields typed below, so extra keys must round-trip through persistence
|
||||
* rather than being dropped.
|
||||
*/
|
||||
export const zAgentSoulModelSettings = z.object({
|
||||
frequency_penalty: z.number().nullish(),
|
||||
|
||||
@@ -2846,7 +2846,6 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -3643,13 +3643,6 @@ export const zAgentModelResponseFormatConfig = z.object({
|
||||
|
||||
/**
|
||||
* AgentSoulModelSettings
|
||||
*
|
||||
* Model parameters for the Agent Soul model.
|
||||
*
|
||||
* Model plugins can declare arbitrary parameters via ``parameter_rules``
|
||||
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
|
||||
* fields typed below, so extra keys must round-trip through persistence
|
||||
* rather than being dropped.
|
||||
*/
|
||||
export const zAgentSoulModelSettings = z.object({
|
||||
frequency_penalty: z.number().nullish(),
|
||||
|
||||
@@ -924,7 +924,6 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -1155,13 +1155,6 @@ export const zAgentModelResponseFormatConfig = z.object({
|
||||
|
||||
/**
|
||||
* AgentSoulModelSettings
|
||||
*
|
||||
* Model parameters for the Agent Soul model.
|
||||
*
|
||||
* Model plugins can declare arbitrary parameters via ``parameter_rules``
|
||||
* (e.g. Qwen/Tongyi's ``enable_thinking``) beyond the common OpenAI-style
|
||||
* fields typed below, so extra keys must round-trip through persistence
|
||||
* rather than being dropped.
|
||||
*/
|
||||
export const zAgentSoulModelSettings = z.object({
|
||||
frequency_penalty: z.number().nullish(),
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import MailAndCodeAuth from '../mail-and-code-auth'
|
||||
|
||||
const pushMock = vi.fn()
|
||||
const replaceMock = vi.fn()
|
||||
const setCountdownLeftTimeMock = vi.fn()
|
||||
const sendWebAppEMailLoginCodeMock = vi.fn()
|
||||
const searchParams = new URLSearchParams({
|
||||
redirect_url: encodeURIComponent('/chatbot/test-app'),
|
||||
})
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: () => ({ push: pushMock, replace: replaceMock }),
|
||||
useSearchParams: () => searchParams,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/signin/storage', () => ({
|
||||
COUNT_DOWN_TIME_MS: 60_000,
|
||||
useSetCountdownLeftTime: () => setCountdownLeftTimeMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
sendWebAppEMailLoginCode: (...args: unknown[]) => sendWebAppEMailLoginCodeMock(...args),
|
||||
}))
|
||||
|
||||
describe('MailAndCodeAuth', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
sendWebAppEMailLoginCodeMock.mockResolvedValue({ result: 'success', data: 'token-abc' })
|
||||
})
|
||||
|
||||
it('submits from the email field through the native form', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<MailAndCodeAuth />)
|
||||
|
||||
const emailInput = screen.getByRole('textbox', { name: 'login.email' })
|
||||
const submitButton = screen.getByRole('button', { name: 'login.signup.verifyMail' })
|
||||
|
||||
expect(emailInput).toHaveAttribute('name', 'email')
|
||||
expect(emailInput).toHaveAttribute('autocomplete', 'email')
|
||||
expect(emailInput).toHaveAttribute('spellcheck', 'false')
|
||||
expect(submitButton).toHaveAttribute('type', 'submit')
|
||||
|
||||
await user.tab()
|
||||
expect(emailInput).toHaveFocus()
|
||||
await user.type(emailInput, 'user@example.com{Enter}')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendWebAppEMailLoginCodeMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
expect(pushMock).toHaveBeenCalledWith(expect.stringContaining('/webapp-signin/check-code?'))
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage'
|
||||
import { emailRegex } from '@/config'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
@@ -64,37 +64,33 @@ export default function MailAndCodeAuth() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
onFormSubmit={() => {
|
||||
void handleGetEMailVerificationCode()
|
||||
}}
|
||||
>
|
||||
<Field name="email" className="mb-2 block">
|
||||
<FieldLabel className="my-2 py-0 system-md-semibold! text-text-secondary">
|
||||
<form onSubmit={noop}>
|
||||
<input type="text" className="hidden" />
|
||||
<div className="mb-2">
|
||||
<label htmlFor="email" className="my-2 system-md-semibold text-text-secondary">
|
||||
{t(($) => $.email, { ns: 'login' })}
|
||||
</FieldLabel>
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<FieldControl
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
spellCheck={false}
|
||||
value={email}
|
||||
placeholder={t(($) => $.emailPlaceholder, { ns: 'login' }) as string}
|
||||
onValueChange={setEmail}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Button
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={loading || !email}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={handleGetEMailVerificationCode}
|
||||
>
|
||||
{t(($) => $['signup.verifyMail'], { ns: 'login' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
</Form>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,17 +88,20 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
})
|
||||
|
||||
it('should render edit and delete buttons', () => {
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const buttons = container.querySelectorAll('button')
|
||||
expect(buttons.length).toBe(2)
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
expect(screen.getByRole('button', { name: 'common.operation.edit' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.operation.delete' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide edit and delete buttons when external knowledge API management is unavailable', () => {
|
||||
const { container } = render(
|
||||
<ExternalKnowledgeAPICard {...defaultProps} canManageExternalKnowledgeApi={false} />,
|
||||
)
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} canManageExternalKnowledgeApi={false} />)
|
||||
|
||||
expect(container.querySelectorAll('button').length).toBe(0)
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.operation.edit' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.operation.delete' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API connection icon', () => {
|
||||
@@ -125,9 +128,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
}
|
||||
vi.mocked(fetchExternalAPI).mockResolvedValue(mockResponse)
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const buttons = container.querySelectorAll('button')
|
||||
const editButton = buttons[0]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = screen.getByRole('button', { name: 'common.operation.edit' })
|
||||
|
||||
fireEvent.click(editButton!)
|
||||
|
||||
@@ -153,9 +155,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
vi.mocked(fetchExternalAPI).mockRejectedValue(new Error('Fetch failed'))
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const buttons = container.querySelectorAll('button')
|
||||
const editButton = buttons[0]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = screen.getByRole('button', { name: 'common.operation.edit' })
|
||||
|
||||
fireEvent.click(editButton!)
|
||||
|
||||
@@ -185,8 +186,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
}
|
||||
vi.mocked(fetchExternalAPI).mockResolvedValue(mockResponse)
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = container.querySelectorAll('button')[0]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = screen.getByRole('button', { name: 'common.operation.edit' })
|
||||
|
||||
fireEvent.click(editButton!)
|
||||
|
||||
@@ -234,8 +235,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
}
|
||||
vi.mocked(fetchExternalAPI).mockResolvedValue(mockResponse)
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = container.querySelectorAll('button')[0]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const editButton = screen.getByRole('button', { name: 'common.operation.edit' })
|
||||
|
||||
fireEvent.click(editButton!)
|
||||
|
||||
@@ -254,9 +255,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
it('should check usage and show confirm dialog when delete button is clicked', async () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: false, count: 0 })
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const buttons = container.querySelectorAll('button')
|
||||
const deleteButton = buttons[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -273,8 +273,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
it('should show usage count in confirm dialog when API is in use', async () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: true, count: 3 })
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -287,8 +287,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: false, count: 0 })
|
||||
vi.mocked(deleteExternalAPI).mockResolvedValue({ result: 'success' })
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -310,8 +310,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
it('should close confirm dialog when cancel is clicked', async () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: false, count: 0 })
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -332,8 +332,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: false, count: 0 })
|
||||
vi.mocked(deleteExternalAPI).mockRejectedValue(new Error('Delete failed'))
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -358,8 +358,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
vi.mocked(checkUsageExternalAPI).mockRejectedValue(new Error('Check failed'))
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
@@ -394,8 +394,8 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
vi.mocked(checkUsageExternalAPI).mockResolvedValue({ is_using: false, count: 0 })
|
||||
vi.mocked(deleteExternalAPI).mockResolvedValue({ result: 'error' })
|
||||
|
||||
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = container.querySelectorAll('button')[1]
|
||||
render(<ExternalKnowledgeAPICard {...defaultProps} />)
|
||||
const deleteButton = screen.getByRole('button', { name: 'common.operation.delete' })
|
||||
|
||||
fireEvent.click(deleteButton!)
|
||||
|
||||
|
||||
@@ -132,16 +132,26 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
</div>
|
||||
{canManageExternalKnowledgeApi && (
|
||||
<div className="flex items-start gap-1">
|
||||
<ActionButton onClick={handleEditClick}>
|
||||
<RiEditLine className="size-4 text-text-tertiary hover:text-text-secondary" />
|
||||
<ActionButton
|
||||
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
|
||||
onClick={handleEditClick}
|
||||
>
|
||||
<RiEditLine
|
||||
aria-hidden
|
||||
className="size-4 text-text-tertiary hover:text-text-secondary"
|
||||
/>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
|
||||
className="hover:bg-state-destructive-hover"
|
||||
onClick={handleDeleteClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<RiDeleteBinLine className="size-4 text-text-tertiary hover:text-text-destructive" />
|
||||
<RiDeleteBinLine
|
||||
aria-hidden
|
||||
className="size-4 text-text-tertiary hover:text-text-destructive"
|
||||
/>
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user