Compare commits
8 Commits
codex/refa
...
codex/refa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5946aac8 | ||
|
|
20d0a2f57e | ||
|
|
7522ae14b2 | ||
|
|
e09eee8fd1 | ||
|
|
b692ddd80c | ||
|
|
5ebb389d26 | ||
|
|
fff6f7cf2f | ||
|
|
516fd12b8d |
@@ -159,8 +159,12 @@ 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), so the layer config
|
||||
# only receives the keys the runtime contract accepts.
|
||||
# (``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``).
|
||||
_AGENT_MODEL_SETTINGS_PASSTHROUGH_KEYS = (
|
||||
"temperature",
|
||||
"top_p",
|
||||
@@ -168,6 +172,7 @@ _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:
|
||||
@@ -177,6 +182,15 @@ 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,6 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -10,7 +9,7 @@ from controllers.common.schema import (
|
||||
register_response_schema_models,
|
||||
)
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import account_initialization_required, setup_required
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, setup_required
|
||||
from fields.base import ResponseModel
|
||||
from libs.login import login_required
|
||||
from services.advanced_prompt_template_service import AdvancedPromptTemplateArgs, AdvancedPromptTemplateService
|
||||
@@ -49,12 +48,12 @@ class AdvancedPromptTemplateList(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self):
|
||||
args = AdvancedPromptTemplateQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(AdvancedPromptTemplateQuery)
|
||||
def get(self, req_data: AdvancedPromptTemplateQuery):
|
||||
prompt_args: AdvancedPromptTemplateArgs = {
|
||||
"app_mode": args.app_mode,
|
||||
"model_mode": args.model_mode,
|
||||
"model_name": args.model_name,
|
||||
"has_context": args.has_context,
|
||||
"app_mode": req_data.app_mode,
|
||||
"model_mode": req_data.model_mode,
|
||||
"model_name": req_data.model_name,
|
||||
"has_context": req_data.has_context,
|
||||
}
|
||||
return AdvancedPromptTemplateService.get_prompt(prompt_args)
|
||||
|
||||
@@ -21,6 +21,7 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -211,12 +212,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(
|
||||
@@ -341,11 +342,11 @@ class AgentLogApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@with_session(write=False)
|
||||
@get_app_model(mode=[AppMode.AGENT_CHAT])
|
||||
def get(self, session: Session, app_model: App):
|
||||
@model_validate(AgentLogQuery)
|
||||
def get(self, req_data: AgentLogQuery, 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, args.conversation_id, args.message_id, session)
|
||||
return AgentService.get_agent_logs(app_model, req_data.conversation_id, req_data.message_id, session)
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/skills/upload")
|
||||
|
||||
@@ -25,6 +25,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -87,14 +88,21 @@ class AgentAppFeatureConfigResource(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def post(self, session: Session, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
@model_validate(AgentAppFeaturesPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: AgentAppFeaturesPayload,
|
||||
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=args.model_dump(exclude_none=True),
|
||||
config=req_data.model_dump(exclude_none=True),
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -33,6 +32,7 @@ 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
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
@model_validate(CreateAppPayload)
|
||||
def post(self, req_data: CreateAppPayload, session: Session, current_tenant_id: str, current_user: Account):
|
||||
"""Create app"""
|
||||
args = CreateAppPayload.model_validate(console_ns.payload)
|
||||
params = CreateAppParams(
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
mode=args.mode,
|
||||
icon_type=args.icon_type,
|
||||
icon=args.icon,
|
||||
icon_background=args.icon_background,
|
||||
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,
|
||||
)
|
||||
|
||||
app_service = AppService()
|
||||
@@ -908,20 +908,20 @@ class AppApi(Resource):
|
||||
@agent_manage_required_for_agent_app
|
||||
@with_session
|
||||
@get_app_model(mode=None)
|
||||
def put(self, session: Session, app_model: App):
|
||||
@model_validate(UpdateAppPayload)
|
||||
def put(self, req_data: UpdateAppPayload, session: Session, app_model: App):
|
||||
"""Update app"""
|
||||
args = UpdateAppPayload.model_validate(console_ns.payload)
|
||||
|
||||
app_service = AppService()
|
||||
|
||||
args_dict: AppService.ArgsDict = {
|
||||
"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,
|
||||
"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,
|
||||
}
|
||||
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)
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App):
|
||||
@model_validate(CopyAppPayload)
|
||||
def post(self, req_data: CopyAppPayload, 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=args.name,
|
||||
description=args.description,
|
||||
icon_type=args.icon_type,
|
||||
icon=args.icon,
|
||||
icon_background=args.icon_background,
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
icon_type=req_data.icon_type,
|
||||
icon=req_data.icon,
|
||||
icon_background=req_data.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
|
||||
def get(self, app_model: App):
|
||||
@model_validate(AppExportQuery)
|
||||
def get(self, req_data: AppExportQuery, 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=args.include_secret,
|
||||
workflow_id=args.workflow_id,
|
||||
include_secret=req_data.include_secret,
|
||||
workflow_id=req_data.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)
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppNamePayload.model_validate(console_ns.payload)
|
||||
@model_validate(AppNamePayload)
|
||||
def post(self, req_data: AppNamePayload, session: Session, app_model: App):
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_name(app_model, args.name, session=session)
|
||||
app_model = app_service.update_app_name(app_model, req_data.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)
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppIconPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(AppIconPayload)
|
||||
def post(self, req_data: AppIconPayload, session: Session, app_model: App):
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_icon(
|
||||
app_model,
|
||||
args.icon or "",
|
||||
args.icon_background or "",
|
||||
args.icon_type,
|
||||
req_data.icon or "",
|
||||
req_data.icon_background or "",
|
||||
req_data.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)
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppSiteStatusPayload.model_validate(console_ns.payload)
|
||||
@model_validate(AppSiteStatusPayload)
|
||||
def post(self, req_data: AppSiteStatusPayload, session: Session, app_model: App):
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_site_status(app_model, args.enable_site, session=session)
|
||||
app_model = app_service.update_app_site_status(app_model, req_data.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)
|
||||
def post(self, session: Session, app_model: App):
|
||||
args = AppApiStatusPayload.model_validate(console_ns.payload)
|
||||
@model_validate(AppApiStatusPayload)
|
||||
def post(self, req_data: AppApiStatusPayload, session: Session, app_model: App):
|
||||
|
||||
app_service = AppService()
|
||||
app_model = app_service.update_app_api_status(app_model, args.enable_api, session=session)
|
||||
app_model = app_service.update_app_api_status(app_model, req_data.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
|
||||
def post(self, app_model: App):
|
||||
@model_validate(AppTracePayload)
|
||||
def post(self, req_data: AppTracePayload, app_model: App):
|
||||
# add app trace
|
||||
args = AppTracePayload.model_validate(console_ns.payload)
|
||||
|
||||
OpsTraceManager.update_app_tracing_config(
|
||||
app_id=app_model.id,
|
||||
enabled=args.enabled,
|
||||
tracing_provider=args.tracing_provider,
|
||||
enabled=req_data.enabled,
|
||||
tracing_provider=req_data.tracing_provider,
|
||||
)
|
||||
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
|
||||
@@ -12,6 +12,7 @@ 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,
|
||||
@@ -83,8 +84,8 @@ class AppImportApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_IMPORT_EXPORT_DSL, resource_required=False)
|
||||
@with_current_user
|
||||
def post(self, current_user: Account | None = None):
|
||||
args = AppImportPayload.model_validate(console_ns.payload)
|
||||
@model_validate(AppImportPayload)
|
||||
def post(self, req_data: AppImportPayload, current_user: Account | None = None):
|
||||
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
|
||||
@@ -96,15 +97,15 @@ class AppImportApi(Resource):
|
||||
try:
|
||||
result = import_service.import_app(
|
||||
account=account,
|
||||
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,
|
||||
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,
|
||||
)
|
||||
except NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
@@ -113,7 +114,7 @@ class AppImportApi(Resource):
|
||||
else:
|
||||
session.commit()
|
||||
|
||||
is_created_app = args.app_id is None and result.status in {
|
||||
is_created_app = req_data.app_id is None and result.status in {
|
||||
ImportStatus.COMPLETED,
|
||||
ImportStatus.COMPLETED_WITH_WARNINGS,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -276,15 +277,15 @@ class ChatMessageTextApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model
|
||||
def post(self, app_model: App):
|
||||
@model_validate(TextToSpeechPayload)
|
||||
def post(self, req_data: TextToSpeechPayload, app_model: App):
|
||||
try:
|
||||
payload = TextToSpeechPayload.model_validate(console_ns.payload)
|
||||
message_ref = None
|
||||
if payload.message_id:
|
||||
if req_data.message_id:
|
||||
app_ref = AppRefService.create_app_ref(app_model)
|
||||
message_ref = AppRefService.create_message_ref(
|
||||
app_ref,
|
||||
payload.message_id,
|
||||
req_data.message_id,
|
||||
account_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -292,8 +293,8 @@ class ChatMessageTextApi(Resource):
|
||||
return AudioService.transcript_tts(
|
||||
app_model=app_model,
|
||||
session=db.session(),
|
||||
text=payload.text,
|
||||
voice=payload.voice,
|
||||
text=req_data.text,
|
||||
voice=req_data.voice,
|
||||
message_ref=message_ref,
|
||||
is_draft=True,
|
||||
)
|
||||
@@ -339,13 +340,12 @@ class TextModesApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@get_app_model
|
||||
def get(self, app_model: App):
|
||||
@model_validate(TextToSpeechVoiceQuery)
|
||||
def get(self, req_data: TextToSpeechVoiceQuery, 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=args.language,
|
||||
language=req_data.language,
|
||||
)
|
||||
|
||||
return dump_response(TextToSpeechVoiceListResponse, response)
|
||||
|
||||
@@ -29,6 +29,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -160,11 +161,11 @@ class CompletionMessageApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_TEST_AND_RUN)
|
||||
@with_session
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
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)
|
||||
@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)
|
||||
|
||||
streaming = args_model.response_mode != "blocking"
|
||||
streaming = req_data.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, request
|
||||
from flask import abort
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy import func, or_
|
||||
@@ -18,6 +18,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -108,17 +109,17 @@ class CompletionConversationApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@with_session(write=False)
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def get(self, session: Session, current_user: Account, app_model: App):
|
||||
args = CompletionConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(CompletionConversationQuery)
|
||||
def get(self, req_data: CompletionConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
|
||||
query = sa.select(Conversation).where(
|
||||
Conversation.app_id == app_model.id, Conversation.mode == "completion", Conversation.is_deleted.is_(False)
|
||||
)
|
||||
|
||||
if args.keyword:
|
||||
if req_data.keyword:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
escaped_keyword = escape_like_pattern(args.keyword)
|
||||
escaped_keyword = escape_like_pattern(req_data.keyword)
|
||||
query = query.join(Message, Message.conversation_id == Conversation.id).where(
|
||||
or_(
|
||||
Message.query.ilike(f"%{escaped_keyword}%", escape="\\"),
|
||||
@@ -130,7 +131,7 @@ class CompletionConversationApi(Resource):
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -142,7 +143,7 @@ class CompletionConversationApi(Resource):
|
||||
query = query.where(Conversation.created_at < end_datetime_utc)
|
||||
|
||||
# FIXME, the type ignore in this file
|
||||
if args.annotation_status == "annotated":
|
||||
if req_data.annotation_status == "annotated":
|
||||
query = (
|
||||
query.options(selectinload(Conversation.message_annotations)) # type: ignore[arg-type]
|
||||
.join( # type: ignore
|
||||
@@ -150,7 +151,7 @@ class CompletionConversationApi(Resource):
|
||||
)
|
||||
.group_by(Conversation.id)
|
||||
)
|
||||
elif args.annotation_status == "not_annotated":
|
||||
elif req_data.annotation_status == "not_annotated":
|
||||
query = (
|
||||
query.outerjoin(MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id)
|
||||
.group_by(Conversation.id)
|
||||
@@ -159,7 +160,7 @@ class CompletionConversationApi(Resource):
|
||||
|
||||
query = query.order_by(Conversation.created_at.desc())
|
||||
|
||||
conversations = paginate_query(query, session=session, page=args.page, per_page=args.limit)
|
||||
conversations = paginate_query(query, session=session, page=req_data.page, per_page=req_data.limit)
|
||||
|
||||
return dump_response(
|
||||
ConversationPaginationResponse,
|
||||
@@ -238,8 +239,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])
|
||||
def get(self, session: Session, current_user: Account, app_model: App):
|
||||
args = ChatConversationQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(ChatConversationQuery)
|
||||
def get(self, req_data: ChatConversationQuery, session: Session, current_user: Account, app_model: App):
|
||||
|
||||
subquery = (
|
||||
sa.select(Conversation.id.label("conversation_id"), EndUser.session_id.label("from_end_user_session_id"))
|
||||
@@ -249,10 +250,10 @@ class ChatConversationApi(Resource):
|
||||
|
||||
query = sa.select(Conversation).where(Conversation.app_id == app_model.id, Conversation.is_deleted.is_(False))
|
||||
|
||||
if args.keyword:
|
||||
if req_data.keyword:
|
||||
from libs.helper import escape_like_pattern
|
||||
|
||||
escaped_keyword = escape_like_pattern(args.keyword)
|
||||
escaped_keyword = escape_like_pattern(req_data.keyword)
|
||||
keyword_filter = f"%{escaped_keyword}%"
|
||||
query = (
|
||||
query.join(
|
||||
@@ -276,12 +277,12 @@ class ChatConversationApi(Resource):
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
if start_datetime_utc:
|
||||
match args.sort_by:
|
||||
match req_data.sort_by:
|
||||
case "updated_at" | "-updated_at":
|
||||
query = query.where(Conversation.updated_at >= start_datetime_utc)
|
||||
case "created_at" | "-created_at" | _:
|
||||
@@ -289,13 +290,13 @@ class ChatConversationApi(Resource):
|
||||
|
||||
if end_datetime_utc:
|
||||
end_datetime_utc = end_datetime_utc.replace(second=59)
|
||||
match args.sort_by:
|
||||
match req_data.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 args.annotation_status:
|
||||
match req_data.annotation_status:
|
||||
case "annotated":
|
||||
query = (
|
||||
query.options(selectinload(Conversation.message_annotations)) # type: ignore[arg-type]
|
||||
@@ -316,7 +317,7 @@ class ChatConversationApi(Resource):
|
||||
if app_model.mode == AppMode.ADVANCED_CHAT:
|
||||
query = query.where(Conversation.invoke_from != InvokeFrom.DEBUGGER)
|
||||
|
||||
match args.sort_by:
|
||||
match req_data.sort_by:
|
||||
case "created_at":
|
||||
query = query.order_by(Conversation.created_at.asc())
|
||||
case "-created_at":
|
||||
@@ -328,7 +329,7 @@ class ChatConversationApi(Resource):
|
||||
case _:
|
||||
query = query.order_by(Conversation.created_at.desc())
|
||||
|
||||
conversations = paginate_query(query, session=session, page=args.page, per_page=args.limit)
|
||||
conversations = paginate_query(query, session=session, page=req_data.page, per_page=req_data.limit)
|
||||
|
||||
return dump_response(
|
||||
ConversationWithSummaryPaginationResponse,
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
@@ -16,6 +15,7 @@ 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)
|
||||
def get(self, app_model: App):
|
||||
args = ConversationVariablesQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(ConversationVariablesQuery)
|
||||
def get(self, req_data: ConversationVariablesQuery, app_model: App):
|
||||
|
||||
stmt = (
|
||||
select(ConversationVariable)
|
||||
.where(ConversationVariable.app_id == app_model.id)
|
||||
.order_by(ConversationVariable.created_at)
|
||||
)
|
||||
stmt = stmt.where(ConversationVariable.conversation_id == args.conversation_id)
|
||||
stmt = stmt.where(ConversationVariable.conversation_id == req_data.conversation_id)
|
||||
|
||||
# NOTE: This is a temporary solution to avoid performance issues.
|
||||
page = 1
|
||||
|
||||
@@ -17,7 +17,12 @@ from controllers.console.app.error import (
|
||||
ProviderQuotaExceededError,
|
||||
)
|
||||
from controllers.console.app.wraps import with_session
|
||||
from controllers.console.wraps import account_initialization_required, setup_required, with_current_tenant_id
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
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
|
||||
@@ -238,11 +243,11 @@ class RuleGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleGeneratePayload.model_validate(console_ns.payload)
|
||||
@model_validate(RuleGeneratePayload)
|
||||
def post(self, req_data: RuleGeneratePayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=args)
|
||||
rules = LLMGenerator.generate_rule_config(tenant_id=current_tenant_id, args=req_data)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
@@ -267,13 +272,13 @@ class RuleCodeGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleCodeGeneratePayload.model_validate(console_ns.payload)
|
||||
@model_validate(RuleCodeGeneratePayload)
|
||||
def post(self, req_data: RuleCodeGeneratePayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
code_result = LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
args=args,
|
||||
args=req_data,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -299,13 +304,13 @@ class RuleStructuredOutputGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = RuleStructuredOutputPayload.model_validate(console_ns.payload)
|
||||
@model_validate(RuleStructuredOutputPayload)
|
||||
def post(self, req_data: RuleStructuredOutputPayload, current_tenant_id: str):
|
||||
|
||||
try:
|
||||
structured_output = LLMGenerator.generate_structured_output(
|
||||
tenant_id=current_tenant_id,
|
||||
args=args,
|
||||
args=req_data,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
@@ -332,36 +337,36 @@ class InstructionGenerateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
@with_session(write=False)
|
||||
def post(self, session: Session, current_tenant_id: str):
|
||||
args = InstructionGeneratePayload.model_validate(console_ns.payload)
|
||||
@model_validate(InstructionGeneratePayload)
|
||||
def post(self, req_data: InstructionGeneratePayload, session: Session, current_tenant_id: str):
|
||||
providers: list[type[CodeNodeProvider]] = [Python3CodeProvider, JavascriptCodeProvider]
|
||||
code_provider: type[CodeNodeProvider] | None = next(
|
||||
(p for p in providers if p.is_accept_language(args.language)), None
|
||||
(p for p in providers if p.is_accept_language(req_data.language)), None
|
||||
)
|
||||
code_template = code_provider.get_default_code() if code_provider else ""
|
||||
try:
|
||||
# Generate from nothing for a workflow node
|
||||
if (args.current in (code_template, "")) and args.node_id != "":
|
||||
if (req_data.current in (code_template, "")) and req_data.node_id != "":
|
||||
app = session.scalar(
|
||||
select(App).where(App.id == args.flow_id, App.tenant_id == current_tenant_id).limit(1)
|
||||
select(App).where(App.id == req_data.flow_id, App.tenant_id == current_tenant_id).limit(1)
|
||||
)
|
||||
if not app:
|
||||
return {"error": f"app {args.flow_id} not found"}, 400
|
||||
return {"error": f"app {req_data.flow_id} not found"}, 400
|
||||
workflow = WorkflowService().get_draft_workflow(app_model=app, session=session)
|
||||
if not workflow:
|
||||
return {"error": f"workflow {args.flow_id} not found"}, 400
|
||||
return {"error": f"workflow {req_data.flow_id} not found"}, 400
|
||||
nodes: Sequence = workflow.graph_dict["nodes"]
|
||||
node = [node for node in nodes if node["id"] == args.node_id]
|
||||
node = [node for node in nodes if node["id"] == req_data.node_id]
|
||||
if len(node) == 0:
|
||||
return {"error": f"node {args.node_id} not found"}, 400
|
||||
return {"error": f"node {req_data.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=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
@@ -369,8 +374,8 @@ class InstructionGenerateApi(Resource):
|
||||
return LLMGenerator.generate_rule_config(
|
||||
current_tenant_id,
|
||||
args=RuleGeneratePayload(
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
no_variable=True,
|
||||
),
|
||||
)
|
||||
@@ -378,31 +383,31 @@ class InstructionGenerateApi(Resource):
|
||||
return LLMGenerator.generate_code(
|
||||
tenant_id=current_tenant_id,
|
||||
args=RuleCodeGeneratePayload(
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
code_language=args.language,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
code_language=req_data.language,
|
||||
),
|
||||
)
|
||||
case _:
|
||||
return {"error": f"invalid node type: {node_type}"}
|
||||
if args.node_id == "" and args.current != "": # For legacy app without a workflow
|
||||
if req_data.node_id == "" and req_data.current != "": # For legacy app without a workflow
|
||||
return LLMGenerator.instruction_modify_legacy(
|
||||
tenant_id=current_tenant_id,
|
||||
flow_id=args.flow_id,
|
||||
current=args.current,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
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,
|
||||
)
|
||||
if args.node_id != "" and args.current != "": # For workflow node
|
||||
if req_data.node_id != "" and req_data.current != "": # For workflow node
|
||||
return LLMGenerator.instruction_modify_workflow(
|
||||
tenant_id=current_tenant_id,
|
||||
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,
|
||||
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,
|
||||
workflow_service=WorkflowService(),
|
||||
)
|
||||
return {"error": "incompatible parameters"}, 400
|
||||
@@ -426,9 +431,9 @@ class InstructionGenerationTemplateApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def post(self):
|
||||
args = InstructionTemplatePayload.model_validate(console_ns.payload)
|
||||
match args.type:
|
||||
@model_validate(InstructionTemplatePayload)
|
||||
def post(self, req_data: InstructionTemplatePayload):
|
||||
match req_data.type:
|
||||
case "prompt":
|
||||
from core.llm_generator.prompts import INSTRUCTION_GENERATE_TEMPLATE_PROMPT
|
||||
|
||||
@@ -438,7 +443,7 @@ class InstructionGenerationTemplateApi(Resource):
|
||||
|
||||
return {"data": INSTRUCTION_GENERATE_TEMPLATE_CODE}
|
||||
case _:
|
||||
raise ValueError(f"Invalid type: {args.type}")
|
||||
raise ValueError(f"Invalid type: {req_data.type}")
|
||||
|
||||
|
||||
def _workflow_instruction_guard(args: WorkflowGeneratePayload) -> tuple[dict, int] | None:
|
||||
@@ -492,24 +497,24 @@ class WorkflowGenerateApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowGeneratePayload.model_validate(console_ns.payload)
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
|
||||
# Reject empty / over-length instructions at the boundary (shared with
|
||||
# the streaming endpoint) before spending a planner+builder roundtrip.
|
||||
guard = _workflow_instruction_guard(args)
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
try:
|
||||
result = WorkflowGeneratorService.generate_workflow_graph(
|
||||
tenant_id=current_tenant_id,
|
||||
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
|
||||
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
|
||||
else None,
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
@@ -547,13 +552,13 @@ class WorkflowInstructionSuggestionsApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowInstructionSuggestionsPayload.model_validate(console_ns.payload)
|
||||
@model_validate(WorkflowInstructionSuggestionsPayload)
|
||||
def post(self, req_data: WorkflowInstructionSuggestionsPayload, current_tenant_id: str):
|
||||
suggestions = LLMGenerator.generate_workflow_instruction_suggestions(
|
||||
tenant_id=current_tenant_id,
|
||||
mode=args.mode,
|
||||
language=args.language,
|
||||
count=args.count,
|
||||
mode=req_data.mode,
|
||||
language=req_data.language,
|
||||
count=req_data.count,
|
||||
)
|
||||
return dump_response(WorkflowInstructionSuggestionsResponse, {"suggestions": suggestions})
|
||||
|
||||
@@ -583,12 +588,12 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str):
|
||||
args = WorkflowGeneratePayload.model_validate(console_ns.payload)
|
||||
@model_validate(WorkflowGeneratePayload)
|
||||
def post(self, req_data: WorkflowGeneratePayload, current_tenant_id: str):
|
||||
|
||||
# Same boundary guards as the blocking endpoint — return a normal 400
|
||||
# JSON for these BEFORE opening the stream.
|
||||
guard = _workflow_instruction_guard(args)
|
||||
guard = _workflow_instruction_guard(req_data)
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
@@ -596,12 +601,14 @@ class WorkflowGenerateStreamApi(Resource):
|
||||
try:
|
||||
for event_name, payload in WorkflowGeneratorService.generate_workflow_graph_stream(
|
||||
tenant_id=current_tenant_id,
|
||||
mode=args.mode,
|
||||
instruction=args.instruction,
|
||||
model_config=args.model_config_data,
|
||||
ideal_output=args.ideal_output,
|
||||
mode=req_data.mode,
|
||||
instruction=req_data.instruction,
|
||||
model_config=req_data.model_config_data,
|
||||
ideal_output=req_data.ideal_output,
|
||||
current_graph=(
|
||||
args.current_graph.model_dump(by_alias=True, exclude_none=True) if args.current_graph else None
|
||||
req_data.current_graph.model_dump(by_alias=True, exclude_none=True)
|
||||
if req_data.current_graph
|
||||
else None
|
||||
),
|
||||
):
|
||||
body = {"event": event_name, **payload}
|
||||
|
||||
@@ -28,6 +28,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -320,8 +321,8 @@ class MessageFeedbackExportApi(Resource):
|
||||
@account_initialization_required
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
@get_app_model
|
||||
def get(self, app_model: App):
|
||||
args = FeedbackExportQuery.model_validate(request.args.to_dict())
|
||||
@model_validate(FeedbackExportQuery)
|
||||
def get(self, req_data: FeedbackExportQuery, app_model: App):
|
||||
|
||||
# Import the service function
|
||||
from services.feedback_service import FeedbackService
|
||||
@@ -330,12 +331,12 @@ class MessageFeedbackExportApi(Resource):
|
||||
export_data = FeedbackService.export_feedbacks(
|
||||
app_model.id,
|
||||
session=db.session(),
|
||||
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,
|
||||
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,
|
||||
)
|
||||
return export_data
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ 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,
|
||||
@@ -98,8 +99,8 @@ class AppSite(Resource):
|
||||
@with_current_user
|
||||
@with_session
|
||||
@get_app_model
|
||||
def post(self, session: Session, current_user: Account, app_model: App):
|
||||
args = AppSiteUpdatePayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(AppSiteUpdatePayload)
|
||||
def post(self, req_data: AppSiteUpdatePayload, session: Session, current_user: Account, app_model: App):
|
||||
site = session.scalar(select(Site).where(Site.app_id == app_model.id).limit(1))
|
||||
if not site:
|
||||
raise NotFound
|
||||
@@ -123,7 +124,7 @@ class AppSite(Resource):
|
||||
"show_workflow_steps",
|
||||
"use_icon_as_answer_icon",
|
||||
]:
|
||||
value = getattr(args, attr_name)
|
||||
value = getattr(req_data, 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, request
|
||||
from flask import abort
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -12,6 +12,7 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -157,8 +158,8 @@ class DailyMessageStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -177,7 +178,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -217,8 +218,8 @@ class DailyConversationStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -237,7 +238,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -276,8 +277,8 @@ class DailyTerminalsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -296,7 +297,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -336,8 +337,8 @@ class DailyTokenCostStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -357,7 +358,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -399,8 +400,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])
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("c.created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -427,7 +428,7 @@ FROM
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -478,8 +479,8 @@ class UserSatisfactionRateStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("m.created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -502,7 +503,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -547,8 +548,8 @@ class AverageResponseTimeStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model(mode=AppMode.COMPLETION)
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -567,7 +568,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -607,8 +608,8 @@ class TokensPerSecondStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = StatisticTimeRangeQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(StatisticTimeRangeQuery)
|
||||
def get(self, req_data: StatisticTimeRangeQuery, account: Account, app_model: App):
|
||||
|
||||
converted_created_at = convert_datetime_to_date("created_at")
|
||||
sql_query = f"""SELECT
|
||||
@@ -630,7 +631,7 @@ WHERE
|
||||
}
|
||||
|
||||
try:
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ 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
|
||||
@@ -14,6 +13,7 @@ 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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowAppLogQuery)
|
||||
def get(self, req_data: WorkflowAppLogQuery, 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=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,
|
||||
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,
|
||||
)
|
||||
|
||||
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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowAppLogQuery)
|
||||
def get(self, req_data: WorkflowAppLogQuery, 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=args.page,
|
||||
limit=args.limit,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
)
|
||||
|
||||
return WorkflowArchivedLogPaginationResponse.model_validate(
|
||||
|
||||
@@ -10,6 +10,7 @@ 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,
|
||||
@@ -239,18 +240,24 @@ class WorkflowCommentListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App):
|
||||
@model_validate(WorkflowCommentCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: WorkflowCommentCreatePayload,
|
||||
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=payload.content,
|
||||
position_x=payload.position_x,
|
||||
position_y=payload.position_y,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
content=req_data.content,
|
||||
position_x=req_data.position_x,
|
||||
position_y=req_data.position_y,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentCreate, result), 201
|
||||
@@ -289,19 +296,26 @@ class WorkflowCommentDetailApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
def put(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str):
|
||||
@model_validate(WorkflowCommentUpdatePayload)
|
||||
def put(
|
||||
self,
|
||||
req_data: WorkflowCommentUpdatePayload,
|
||||
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=payload.content,
|
||||
position_x=payload.position_x,
|
||||
position_y=payload.position_y,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
content=req_data.content,
|
||||
position_x=req_data.position_x,
|
||||
position_y=req_data.position_y,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentUpdate, result)
|
||||
@@ -372,20 +386,26 @@ class WorkflowCommentReplyApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
def post(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str):
|
||||
@model_validate(WorkflowCommentReplyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: WorkflowCommentReplyPayload,
|
||||
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=payload.content,
|
||||
content=req_data.content,
|
||||
created_by=current_user.id,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
mentioned_user_ids=req_data.mentioned_user_ids,
|
||||
)
|
||||
|
||||
return dump_response(WorkflowCommentReplyCreate, result), 201
|
||||
@@ -407,23 +427,30 @@ class WorkflowCommentReplyDetailApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@get_app_model()
|
||||
def put(self, current_tenant_id: str, current_user: Account, app_model: App, comment_id: str, reply_id: str):
|
||||
@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,
|
||||
):
|
||||
"""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=payload.content,
|
||||
mentioned_user_ids=payload.mentioned_user_ids,
|
||||
content=req_data.content,
|
||||
mentioned_user_ids=req_data.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, request
|
||||
from flask import Response
|
||||
from flask_restx import Resource, fields, marshal, marshal_with
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -22,6 +22,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -359,11 +360,11 @@ class WorkflowVariableCollectionApi(Resource):
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_list_without_value_model)
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
|
||||
def get(self, current_user: Account, app_model: App):
|
||||
@model_validate(WorkflowDraftVariableListQuery)
|
||||
def get(self, req_data: WorkflowDraftVariableListQuery, 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()
|
||||
@@ -378,8 +379,8 @@ class WorkflowVariableCollectionApi(Resource):
|
||||
)
|
||||
workflow_vars = draft_var_srv.list_variables_without_values(
|
||||
app_id=app_model.id,
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -479,7 +480,14 @@ class VariableApi(Resource):
|
||||
@console_ns.response(404, "Variable not found")
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_model)
|
||||
def patch(self, current_user: Account, app_model: App, variable_id: UUID):
|
||||
@model_validate(WorkflowDraftVariableUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowDraftVariableUpdatePayload,
|
||||
current_user: Account,
|
||||
app_model: App,
|
||||
variable_id: UUID,
|
||||
):
|
||||
# Request payload for file types:
|
||||
#
|
||||
# Local File:
|
||||
@@ -504,7 +512,6 @@ 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(
|
||||
@@ -514,8 +521,8 @@ class VariableApi(Resource):
|
||||
current_user_id=current_user.id,
|
||||
)
|
||||
|
||||
new_name = args_model.name
|
||||
raw_value = args_model.value
|
||||
new_name = req_data.name
|
||||
raw_value = req_data.value
|
||||
if new_name is None and raw_value is None:
|
||||
return variable
|
||||
|
||||
@@ -660,13 +667,13 @@ class ConversationVariableCollectionApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
||||
@with_current_user
|
||||
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
payload = ConversationVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(ConversationVariableUpdatePayload)
|
||||
def post(self, req_data: ConversationVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
conversation_variables_list = [
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in payload.conversation_variables
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in req_data.conversation_variables
|
||||
]
|
||||
conversation_variables = [
|
||||
variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list
|
||||
@@ -755,24 +762,24 @@ class EnvironmentVariableCollectionApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_EDIT)
|
||||
@with_current_user
|
||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
payload = EnvironmentVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(EnvironmentVariableUpdatePayload)
|
||||
def post(self, req_data: EnvironmentVariableUpdatePayload, current_user: Account, app_model: App):
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
environment_variables_list = [
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in payload.environment_variables
|
||||
variable.model_dump(mode="json", exclude_unset=True) for variable in req_data.environment_variables
|
||||
]
|
||||
environment_variables = [
|
||||
variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list
|
||||
]
|
||||
|
||||
if payload.patch:
|
||||
if req_data.patch:
|
||||
workflow_service.patch_draft_workflow_environment_variables(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
environment_variables=environment_variables,
|
||||
deleted_environment_variable_ids=payload.deleted_environment_variable_ids,
|
||||
deleted_environment_variable_ids=req_data.deleted_environment_variable_ids,
|
||||
session=db.session(),
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -2,7 +2,6 @@ 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
|
||||
@@ -17,6 +16,7 @@ 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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowRunListQuery)
|
||||
def get(self, req_data: WorkflowRunListQuery, app_model: App):
|
||||
"""
|
||||
Get advanced chat app workflow run list
|
||||
"""
|
||||
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
|
||||
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
|
||||
|
||||
# Default to DEBUGGING if not specified
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowRunCountQuery)
|
||||
def get(self, req_data: WorkflowRunCountQuery, app_model: App):
|
||||
"""
|
||||
Get advanced chat workflow runs count statistics
|
||||
"""
|
||||
args_model = WorkflowRunCountQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args = args_model.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
# Default to DEBUGGING if not specified
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowRunListQuery)
|
||||
def get(self, req_data: WorkflowRunListQuery, app_model: App):
|
||||
"""
|
||||
Get workflow run list
|
||||
"""
|
||||
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
|
||||
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
|
||||
|
||||
# Default to DEBUGGING for workflow if not specified (backward compatibility)
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.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])
|
||||
def get(self, app_model: App):
|
||||
@model_validate(WorkflowRunCountQuery)
|
||||
def get(self, req_data: WorkflowRunCountQuery, app_model: App):
|
||||
"""
|
||||
Get workflow runs count statistics
|
||||
"""
|
||||
args_model = WorkflowRunCountQuery.model_validate(request.args.to_dict(flat=True))
|
||||
args = args_model.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
# Default to DEBUGGING for workflow if not specified (backward compatibility)
|
||||
triggered_from = (
|
||||
WorkflowRunTriggeredFrom(args_model.triggered_from)
|
||||
if args_model.triggered_from
|
||||
WorkflowRunTriggeredFrom(req_data.triggered_from)
|
||||
if req_data.triggered_from
|
||||
else WorkflowRunTriggeredFrom.DEBUGGING
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import abort, jsonify, request
|
||||
from flask import abort, jsonify
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -10,6 +10,7 @@ from controllers.console.wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -104,13 +105,13 @@ class WorkflowDailyRunsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -148,13 +149,13 @@ class WorkflowDailyTerminalsStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -192,13 +193,13 @@ class WorkflowDailyTokenCostStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
@@ -236,13 +237,13 @@ class WorkflowAverageAppInteractionStatistic(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_MONITOR)
|
||||
@get_app_model(mode=[AppMode.WORKFLOW])
|
||||
def get(self, account: Account, app_model: App):
|
||||
args = WorkflowStatisticQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(WorkflowStatisticQuery)
|
||||
def get(self, req_data: WorkflowStatisticQuery, account: Account, app_model: App):
|
||||
|
||||
assert account.timezone is not None
|
||||
|
||||
try:
|
||||
start_date, end_date = parse_time_range(args.start, args.end, account.timezone)
|
||||
start_date, end_date = parse_time_range(req_data.start, req_data.end, account.timezone)
|
||||
except ValueError as e:
|
||||
abort(400, description=str(e))
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -25,6 +24,7 @@ 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)
|
||||
def get(self, app_model: App):
|
||||
@model_validate(Parser)
|
||||
def get(self, req_data: Parser, app_model: App):
|
||||
"""Get webhook trigger for a node"""
|
||||
args = Parser.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
node_id = args.node_id
|
||||
node_id = req_data.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)
|
||||
def post(self, current_tenant_id: str, app_model: App):
|
||||
@model_validate(ParserEnable)
|
||||
def post(self, req_data: ParserEnable, current_tenant_id: str, app_model: App):
|
||||
"""Update app trigger (enable/disable)"""
|
||||
args = ParserEnable.model_validate(console_ns.payload)
|
||||
|
||||
trigger_id = args.trigger_id
|
||||
trigger_id = req_data.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 args.enable_trigger else AppTriggerStatus.DISABLED
|
||||
trigger.status = AppTriggerStatus.ENABLED if req_data.enable_trigger else AppTriggerStatus.DISABLED
|
||||
|
||||
# Add computed icon field
|
||||
url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/"
|
||||
|
||||
@@ -36,6 +36,7 @@ from ..wraps import (
|
||||
RBACPermission,
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -229,12 +230,18 @@ class DataSourceNotionListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session(write=False)
|
||||
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))
|
||||
@model_validate(DataSourceNotionListQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: DataSourceNotionListQuery,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
) -> tuple[dict[str, Any], int]:
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
credential = datasource_provider_service.get_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
credential_id=query.credential_id,
|
||||
credential_id=req_data.credential_id,
|
||||
provider="notion_datasource",
|
||||
plugin_id="langgenius/notion_datasource",
|
||||
)
|
||||
@@ -242,8 +249,8 @@ class DataSourceNotionListApi(Resource):
|
||||
raise NotFound("Credential not found.")
|
||||
exist_page_ids = []
|
||||
# import notion in the exist dataset
|
||||
if query.dataset_id:
|
||||
dataset = DatasetService.get_dataset(query.dataset_id, session)
|
||||
if req_data.dataset_id:
|
||||
dataset = DatasetService.get_dataset(req_data.dataset_id, session)
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
if dataset.data_source_type != "notion_import":
|
||||
@@ -251,7 +258,7 @@ class DataSourceNotionListApi(Resource):
|
||||
|
||||
documents = session.scalars(
|
||||
select(Document).where(
|
||||
Document.dataset_id == query.dataset_id,
|
||||
Document.dataset_id == req_data.dataset_id,
|
||||
Document.tenant_id == current_tenant_id,
|
||||
Document.data_source_type == "notion_import",
|
||||
Document.enabled.is_(True),
|
||||
@@ -318,13 +325,19 @@ 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
|
||||
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))
|
||||
@model_validate(DataSourceNotionPreviewQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: DataSourceNotionPreviewQuery,
|
||||
current_tenant_id: str,
|
||||
page_id: UUID,
|
||||
page_type: str,
|
||||
) -> tuple[dict[str, str], int]:
|
||||
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
credential = datasource_provider_service.get_datasource_credentials(
|
||||
tenant_id=current_tenant_id,
|
||||
credential_id=query.credential_id,
|
||||
credential_id=req_data.credential_id,
|
||||
provider="notion_datasource",
|
||||
plugin_id="langgenius/notion_datasource",
|
||||
)
|
||||
@@ -354,12 +367,17 @@ class DataSourceNotionIndexingEstimateApi(Resource):
|
||||
@console_ns.response(200, "Success", console_ns.models[IndexingEstimate.__name__])
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
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()
|
||||
@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()
|
||||
# validate args
|
||||
DocumentService.estimate_args_validate(args)
|
||||
notion_info_list = payload.notion_info_list
|
||||
notion_info_list = req_data.notion_info_list
|
||||
extract_settings = []
|
||||
for notion_info in notion_info_list:
|
||||
workspace_id = notion_info["workspace_id"]
|
||||
|
||||
@@ -26,6 +26,7 @@ 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,
|
||||
@@ -572,9 +573,8 @@ class DatasetListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
payload = DatasetCreatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
@model_validate(DatasetCreatePayload)
|
||||
def post(self, req_data: DatasetCreatePayload, 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, 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 = payload.permission or DatasetPermissionEnum.ONLY_ME
|
||||
permission = req_data.permission or DatasetPermissionEnum.ONLY_ME
|
||||
|
||||
try:
|
||||
dataset = DatasetService.create_empty_dataset(
|
||||
session=session,
|
||||
tenant_id=current_tenant_id,
|
||||
name=payload.name,
|
||||
description=payload.description,
|
||||
indexing_technique=payload.indexing_technique,
|
||||
name=req_data.name,
|
||||
description=req_data.description,
|
||||
indexing_technique=req_data.indexing_technique,
|
||||
account=current_user,
|
||||
permission=permission,
|
||||
provider=payload.provider,
|
||||
external_knowledge_api_id=payload.external_knowledge_api_id,
|
||||
external_knowledge_id=payload.external_knowledge_id,
|
||||
provider=req_data.provider,
|
||||
external_knowledge_api_id=req_data.external_knowledge_api_id,
|
||||
external_knowledge_id=req_data.external_knowledge_id,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
raise DatasetNameDuplicateError()
|
||||
@@ -715,28 +715,35 @@ class DatasetApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
def patch(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||
@model_validate(DatasetUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: DatasetUpdatePayload,
|
||||
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 (
|
||||
payload.indexing_technique == IndexTechniqueType.HIGH_QUALITY
|
||||
and payload.embedding_model_provider is not None
|
||||
and payload.embedding_model is not None
|
||||
req_data.indexing_technique == IndexTechniqueType.HIGH_QUALITY
|
||||
and req_data.embedding_model_provider is not None
|
||||
and req_data.embedding_model is not None
|
||||
):
|
||||
is_multimodal = DatasetService.check_is_multimodal_model(
|
||||
dataset.tenant_id, payload.embedding_model_provider, payload.embedding_model
|
||||
dataset.tenant_id, req_data.embedding_model_provider, req_data.embedding_model
|
||||
)
|
||||
payload.is_multimodal = is_multimodal
|
||||
payload_data = payload.model_dump(exclude_unset=True)
|
||||
req_data.is_multimodal = is_multimodal
|
||||
payload_data = req_data.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, payload.permission, payload.partial_member_list, session=session
|
||||
current_user, dataset, req_data.permission, req_data.partial_member_list, session=session
|
||||
)
|
||||
|
||||
dataset = DatasetService.update_dataset(dataset_id_str, payload_data, current_user, session=session)
|
||||
@@ -754,12 +761,12 @@ class DatasetApi(Resource):
|
||||
result_data["permission_keys"] = permission_keys_map.get(dataset_id_str, [])
|
||||
tenant_id = current_tenant_id
|
||||
|
||||
if payload.partial_member_list is not None and payload.permission == DatasetPermissionEnum.PARTIAL_TEAM:
|
||||
if req_data.partial_member_list is not None and req_data.permission == DatasetPermissionEnum.PARTIAL_TEAM:
|
||||
DatasetPermissionService.update_partial_member_list(
|
||||
tenant_id, dataset_id_str, payload.partial_member_list, session
|
||||
tenant_id, dataset_id_str, req_data.partial_member_list, session
|
||||
)
|
||||
# clear partial member list when permission is only_me or all_team_members
|
||||
elif payload.permission in {DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM}:
|
||||
elif req_data.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)
|
||||
@@ -872,9 +879,9 @@ class DatasetIndexingEstimateApi(Resource):
|
||||
@console_ns.expect(console_ns.models[IndexingEstimatePayload.__name__])
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def post(self, session: Session, current_tenant_id: str):
|
||||
payload = IndexingEstimatePayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump()
|
||||
@model_validate(IndexingEstimatePayload)
|
||||
def post(self, req_data: IndexingEstimatePayload, session: Session, current_tenant_id: str):
|
||||
args = req_data.model_dump()
|
||||
# validate args
|
||||
DocumentService.estimate_args_validate(args)
|
||||
extract_settings = []
|
||||
|
||||
@@ -36,6 +36,7 @@ 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,
|
||||
@@ -414,8 +415,10 @@ 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,
|
||||
@@ -455,8 +458,7 @@ class DatasetDocumentSegmentAddApi(Resource):
|
||||
except services.errors.account.NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
# validate args
|
||||
payload = SegmentCreatePayload.model_validate(console_ns.payload or {})
|
||||
payload_dict = payload.model_dump(exclude_none=True)
|
||||
payload_dict = req_data.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(
|
||||
@@ -485,8 +487,10 @@ 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,
|
||||
@@ -532,13 +536,12 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
||||
segment_id_str = str(segment_id)
|
||||
_, segment = _get_segment_for_document(session, dataset, document, segment_id_str)
|
||||
# validate args
|
||||
payload = SegmentUpdatePayload.model_validate(console_ns.payload or {})
|
||||
payload_dict = payload.model_dump(exclude_none=True)
|
||||
payload_dict = req_data.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(payload.model_dump(exclude_none=True)),
|
||||
SegmentUpdateArgs.model_validate(req_data.model_dump(exclude_none=True)),
|
||||
segment,
|
||||
document,
|
||||
dataset,
|
||||
@@ -616,8 +619,10 @@ 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,
|
||||
@@ -635,8 +640,7 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
payload = BatchImportPayload.model_validate(console_ns.payload or {})
|
||||
upload_file_id = payload.upload_file_id
|
||||
upload_file_id = req_data.upload_file_id
|
||||
|
||||
upload_file = session.scalar(select(UploadFile).where(UploadFile.id == upload_file_id).limit(1))
|
||||
if not upload_file:
|
||||
@@ -697,8 +701,10 @@ 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,
|
||||
@@ -742,8 +748,7 @@ class ChildChunkAddApi(Resource):
|
||||
_, segment = _get_segment_for_document(session, dataset, document, segment_id_str)
|
||||
# validate args
|
||||
try:
|
||||
payload = ChildChunkCreatePayload.model_validate(console_ns.payload or {})
|
||||
child_chunk = SegmentService.create_child_chunk(payload.content, segment, document, dataset, session)
|
||||
child_chunk = SegmentService.create_child_chunk(req_data.content, segment, document, dataset, session)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
return dump_response(ChildChunkDetailResponse, {"data": child_chunk}), 200
|
||||
@@ -812,8 +817,10 @@ 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,
|
||||
@@ -843,9 +850,8 @@ 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(payload.chunks, segment, document, dataset, session)
|
||||
child_chunks = SegmentService.update_child_chunks(req_data.chunks, segment, document, dataset, session)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
return dump_response(ChildChunkBatchUpdateResponse, {"data": child_chunks}), 200
|
||||
@@ -918,8 +924,10 @@ 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,
|
||||
@@ -955,9 +963,8 @@ 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(
|
||||
payload.content, child_chunk, segment, document, dataset, session
|
||||
req_data.content, child_chunk, segment, document, dataset, session
|
||||
)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -26,6 +25,7 @@ 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)
|
||||
def get(self, session: Session, current_tenant_id: str):
|
||||
query = ExternalApiTemplateListQuery.model_validate(request.args.to_dict())
|
||||
@model_validate(ExternalApiTemplateListQuery)
|
||||
def get(self, req_data: ExternalApiTemplateListQuery, session: Session, current_tenant_id: str):
|
||||
|
||||
external_knowledge_apis, total = ExternalDatasetService.get_external_knowledge_apis(
|
||||
query.page, query.limit, current_tenant_id, query.keyword, session=session
|
||||
req_data.page, req_data.limit, current_tenant_id, req_data.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) == query.limit,
|
||||
limit=query.limit,
|
||||
has_more=len(external_knowledge_apis) == req_data.limit,
|
||||
limit=req_data.limit,
|
||||
total=total,
|
||||
page=query.page,
|
||||
page=req_data.page,
|
||||
).model_dump(mode="json"), 200
|
||||
|
||||
@console_ns.doc("create_external_api_template")
|
||||
@@ -215,10 +215,16 @@ class ExternalApiTemplateListApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
payload = ExternalKnowledgeApiPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(ExternalKnowledgeApiPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: ExternalKnowledgeApiPayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
):
|
||||
|
||||
ExternalDatasetService.validate_api_list(payload.settings)
|
||||
ExternalDatasetService.validate_api_list(req_data.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:
|
||||
@@ -228,7 +234,7 @@ class ExternalApiTemplateListApi(Resource):
|
||||
external_knowledge_api = ExternalDatasetService.create_external_knowledge_api(
|
||||
tenant_id=current_tenant_id,
|
||||
user_id=current_user.id,
|
||||
args=payload.model_dump(),
|
||||
args=req_data.model_dump(),
|
||||
session=session,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
@@ -279,17 +285,24 @@ class ExternalApiTemplateApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def patch(self, session: Session, current_tenant_id: str, current_user: Account, external_knowledge_api_id: UUID):
|
||||
@model_validate(ExternalKnowledgeApiPayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: ExternalKnowledgeApiPayload,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
external_knowledge_api_id: UUID,
|
||||
):
|
||||
external_knowledge_api_id_str = str(external_knowledge_api_id)
|
||||
|
||||
payload = ExternalKnowledgeApiPayload.model_validate(console_ns.payload or {})
|
||||
ExternalDatasetService.validate_api_list(payload.settings)
|
||||
ExternalDatasetService.validate_api_list(req_data.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=payload.model_dump(),
|
||||
args=req_data.model_dump(),
|
||||
session=session,
|
||||
)
|
||||
|
||||
@@ -354,9 +367,15 @@ class ExternalDatasetCreateApi(Resource):
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account):
|
||||
@model_validate(ExternalDatasetCreatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: ExternalDatasetCreatePayload,
|
||||
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:
|
||||
@@ -366,7 +385,7 @@ class ExternalDatasetCreateApi(Resource):
|
||||
dataset = ExternalDatasetService.create_external_dataset(
|
||||
tenant_id=current_tenant_id,
|
||||
user_id=current_user.id,
|
||||
args=payload,
|
||||
args=req_data,
|
||||
session=session,
|
||||
)
|
||||
except services.errors.dataset.DatasetNameDuplicateError:
|
||||
@@ -405,7 +424,8 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_PIPELINE_TEST)
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||
@model_validate(ExternalHitTestingPayload)
|
||||
def post(self, req_data: ExternalHitTestingPayload, 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:
|
||||
@@ -416,17 +436,16 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
||||
except services.errors.account.NoPermissionError as e:
|
||||
raise Forbidden(str(e))
|
||||
|
||||
payload = ExternalHitTestingPayload.model_validate(console_ns.payload or {})
|
||||
HitTestingService.hit_testing_args_check(payload.model_dump())
|
||||
HitTestingService.hit_testing_args_check(req_data.model_dump())
|
||||
|
||||
try:
|
||||
response = HitTestingService.external_retrieve(
|
||||
session=session,
|
||||
dataset=dataset,
|
||||
query=payload.query,
|
||||
query=req_data.query,
|
||||
account=current_user,
|
||||
external_retrieval_model=payload.external_retrieval_model,
|
||||
metadata_filtering_conditions=payload.metadata_filtering_conditions,
|
||||
external_retrieval_model=req_data.external_retrieval_model,
|
||||
metadata_filtering_conditions=req_data.metadata_filtering_conditions,
|
||||
)
|
||||
|
||||
return dump_response(ExternalHitTestingResponse, response)
|
||||
@@ -441,11 +460,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__])
|
||||
def post(self):
|
||||
payload = BedrockRetrievalPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(BedrockRetrievalPayload)
|
||||
def post(self, req_data: BedrockRetrievalPayload):
|
||||
|
||||
# Call the knowledge retrieval service
|
||||
result = ExternalDatasetTestService.knowledge_retrieval(
|
||||
payload.retrieval_setting, payload.query, payload.knowledge_id
|
||||
req_data.retrieval_setting, req_data.query, req_data.knowledge_id
|
||||
)
|
||||
return dump_response(BedrockRetrievalResponse, result), 200
|
||||
|
||||
@@ -14,6 +14,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
enterprise_license_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -59,9 +60,15 @@ class DatasetMetadataCreateApi(Resource):
|
||||
@with_current_tenant_id
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
def post(self, session: Session, current_tenant_id: str, current_user: Account, dataset_id: UUID):
|
||||
metadata_args = MetadataArgs.model_validate(console_ns.payload or {})
|
||||
|
||||
@model_validate(MetadataArgs)
|
||||
def post(
|
||||
self,
|
||||
req_data: MetadataArgs,
|
||||
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:
|
||||
@@ -69,7 +76,7 @@ class DatasetMetadataCreateApi(Resource):
|
||||
DatasetService.check_dataset_permission(dataset, current_user, session)
|
||||
|
||||
metadata = MetadataService.create_metadata(
|
||||
dataset_id_str, metadata_args, current_user, current_tenant_id, session=session
|
||||
dataset_id_str, req_data, current_user, current_tenant_id, session=session
|
||||
)
|
||||
return dump_response(DatasetMetadataResponse, metadata), 201
|
||||
|
||||
@@ -103,16 +110,17 @@ 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,
|
||||
):
|
||||
payload = MetadataUpdatePayload.model_validate(console_ns.payload or {})
|
||||
name = payload.name
|
||||
name = req_data.name
|
||||
|
||||
dataset_id_str = str(dataset_id)
|
||||
metadata_id_str = str(metadata_id)
|
||||
@@ -203,16 +211,15 @@ class DocumentMetadataEditApi(Resource):
|
||||
@with_current_user
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, dataset_id: UUID):
|
||||
@model_validate(MetadataOperationData)
|
||||
def post(self, req_data: MetadataOperationData, 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)
|
||||
|
||||
metadata_args = MetadataOperationData.model_validate(console_ns.payload or {})
|
||||
|
||||
MetadataService.update_documents_metadata(dataset, metadata_args, current_user, session=session)
|
||||
MetadataService.update_documents_metadata(dataset, req_data, current_user, session=session)
|
||||
|
||||
# Frontend callers only await success and invalidate caches; no response body is consumed.
|
||||
return "", 204
|
||||
|
||||
@@ -14,6 +14,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -275,8 +276,8 @@ class DatasourceAuth(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_CREATE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceCredentialPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(DatasourceCredentialPayload)
|
||||
def post(self, req_data: DatasourceCredentialPayload, current_tenant_id: str, provider_id: str):
|
||||
datasource_provider_id = DatasourceProviderID(provider_id)
|
||||
datasource_provider_service = DatasourceProviderService()
|
||||
|
||||
@@ -284,8 +285,8 @@ class DatasourceAuth(Resource):
|
||||
datasource_provider_service.add_datasource_api_key_provider(
|
||||
tenant_id=current_tenant_id,
|
||||
provider_id=datasource_provider_id,
|
||||
credentials=payload.credentials,
|
||||
name=payload.name,
|
||||
credentials=req_data.credentials,
|
||||
name=req_data.name,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
raise ValueError(str(ex))
|
||||
@@ -325,16 +326,16 @@ class DatasourceAuthDeleteApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
@model_validate(DatasourceCredentialDeletePayload)
|
||||
def post(self, req_data: DatasourceCredentialDeletePayload, 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=payload.credential_id,
|
||||
auth_id=req_data.credential_id,
|
||||
provider=provider_name,
|
||||
plugin_id=plugin_id,
|
||||
session=db.session(),
|
||||
@@ -354,18 +355,18 @@ class DatasourceAuthUpdateApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
@model_validate(DatasourceCredentialUpdatePayload)
|
||||
def post(self, req_data: DatasourceCredentialUpdatePayload, 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=payload.credential_id,
|
||||
auth_id=req_data.credential_id,
|
||||
provider=datasource_provider_id.provider_name,
|
||||
plugin_id=datasource_provider_id.plugin_id,
|
||||
credentials=payload.credentials or {},
|
||||
name=payload.name,
|
||||
credentials=req_data.credentials or {},
|
||||
name=req_data.name,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 201
|
||||
|
||||
@@ -420,15 +421,15 @@ class DatasourceAuthOauthCustomClient(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceCustomClientPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(DatasourceCustomClientPayload)
|
||||
def post(self, req_data: DatasourceCustomClientPayload, current_tenant_id: str, provider_id: str):
|
||||
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=payload.client_params or {},
|
||||
enabled=payload.enable_oauth_custom_client or False,
|
||||
client_params=req_data.client_params or {},
|
||||
enabled=req_data.enable_oauth_custom_client or False,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
|
||||
@@ -457,14 +458,14 @@ class DatasourceAuthDefaultApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceDefaultPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(DatasourceDefaultPayload)
|
||||
def post(self, req_data: DatasourceDefaultPayload, current_tenant_id: str, provider_id: str):
|
||||
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=payload.id,
|
||||
credential_id=req_data.id,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
|
||||
|
||||
@@ -479,14 +480,14 @@ class DatasourceUpdateProviderNameApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.CREDENTIAL_MANAGE, resource_required=False)
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, provider_id: str):
|
||||
payload = DatasourceUpdateNamePayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(DatasourceUpdateNamePayload)
|
||||
def post(self, req_data: DatasourceUpdateNamePayload, current_tenant_id: str, provider_id: str):
|
||||
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=payload.name,
|
||||
credential_id=payload.credential_id,
|
||||
name=req_data.name,
|
||||
credential_id=req_data.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, setup_required, with_current_user
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, 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
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(Parser)
|
||||
def post(self, req_data: Parser, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run datasource content preview
|
||||
"""
|
||||
args = Parser.model_validate(console_ns.payload)
|
||||
|
||||
inputs = args.inputs
|
||||
datasource_type = args.datasource_type
|
||||
inputs = req_data.inputs
|
||||
datasource_type = req_data.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=args.credential_id,
|
||||
credential_id=req_data.credential_id,
|
||||
)
|
||||
return preview_content, 200
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
@@ -21,6 +20,7 @@ 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,12 +104,17 @@ class PipelineTemplateListApi(Resource):
|
||||
@enterprise_license_required
|
||||
@with_current_tenant_id
|
||||
@with_session
|
||||
def get(self, session: Session, current_tenant_id: str) -> JsonResponseWithStatus:
|
||||
query = PipelineTemplateListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(PipelineTemplateListQuery)
|
||||
def get(
|
||||
self,
|
||||
req_data: PipelineTemplateListQuery,
|
||||
session: Session,
|
||||
current_tenant_id: str,
|
||||
) -> JsonResponseWithStatus:
|
||||
# get pipeline templates
|
||||
pipeline_templates = RagPipelineService.get_pipeline_templates(
|
||||
type=query.type,
|
||||
language=query.language,
|
||||
type=req_data.type,
|
||||
language=req_data.language,
|
||||
current_tenant_id=current_tenant_id,
|
||||
session=session,
|
||||
)
|
||||
@@ -125,11 +130,11 @@ class PipelineTemplateDetailApi(Resource):
|
||||
@account_initialization_required
|
||||
@enterprise_license_required
|
||||
@with_session
|
||||
def get(self, session: Session, template_id: str) -> JsonResponseWithStatus:
|
||||
query = PipelineTemplateDetailQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(PipelineTemplateDetailQuery)
|
||||
def get(self, req_data: PipelineTemplateDetailQuery, session: Session, template_id: str) -> JsonResponseWithStatus:
|
||||
pipeline_template = RagPipelineService.get_pipeline_template_detail(
|
||||
template_id,
|
||||
type=query.type,
|
||||
type=req_data.type,
|
||||
session=session,
|
||||
)
|
||||
if pipeline_template is None:
|
||||
@@ -147,9 +152,15 @@ class CustomizedPipelineTemplateApi(Resource):
|
||||
@enterprise_license_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
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())
|
||||
@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())
|
||||
RagPipelineService.update_customized_pipeline_template(
|
||||
template_id, pipeline_template_info, current_user, current_tenant_id, session=db.session()
|
||||
)
|
||||
@@ -192,10 +203,16 @@ class PublishCustomizedPipelineTemplateApi(Resource):
|
||||
@knowledge_pipeline_publish_enabled
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account, pipeline_id: str) -> tuple[str, int]:
|
||||
payload = CustomizedPipelineTemplatePayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(CustomizedPipelineTemplatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: CustomizedPipelineTemplatePayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
pipeline_id: str,
|
||||
) -> tuple[str, int]:
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
rag_pipeline_service.publish_customized_pipeline_template(
|
||||
pipeline_id, payload.model_dump(), current_user, current_tenant_id, session=db.session()
|
||||
pipeline_id, req_data.model_dump(), current_user, current_tenant_id, session=db.session()
|
||||
)
|
||||
return "", 204
|
||||
|
||||
@@ -10,6 +10,7 @@ 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,
|
||||
@@ -47,8 +48,13 @@ class CreateRagPipelineDatasetApi(Resource):
|
||||
@cloud_edition_billing_rate_limit_check("knowledge")
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account) -> JsonResponseWithStatus:
|
||||
payload = RagPipelineDatasetImportPayload.model_validate(console_ns.payload or {})
|
||||
@model_validate(RagPipelineDatasetImportPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: RagPipelineDatasetImportPayload,
|
||||
current_tenant_id: str,
|
||||
current_user: Account,
|
||||
) -> JsonResponseWithStatus:
|
||||
# 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()
|
||||
@@ -62,7 +68,7 @@ class CreateRagPipelineDatasetApi(Resource):
|
||||
),
|
||||
permission=DatasetPermissionEnum.ONLY_ME,
|
||||
partial_member_list=None,
|
||||
yaml_content=payload.yaml_content,
|
||||
yaml_content=req_data.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, request
|
||||
from flask import Response
|
||||
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, setup_required, with_current_user
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, 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)
|
||||
def get(self, current_user: Account, pipeline: Pipeline):
|
||||
@model_validate(PaginationQuery)
|
||||
def get(self, req_data: PaginationQuery, 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=query.page,
|
||||
limit=query.limit,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
@@ -196,7 +196,14 @@ class RagPipelineVariableApi(Resource):
|
||||
@_api_prerequisite
|
||||
@marshal_with(workflow_draft_variable_model)
|
||||
@console_ns.expect(console_ns.models[WorkflowDraftVariablePatchPayload.__name__])
|
||||
def patch(self, _current_user: Account, pipeline: Pipeline, variable_id: UUID):
|
||||
@model_validate(WorkflowDraftVariablePatchPayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowDraftVariablePatchPayload,
|
||||
_current_user: Account,
|
||||
pipeline: Pipeline,
|
||||
variable_id: UUID,
|
||||
):
|
||||
# Request payload for file types:
|
||||
#
|
||||
# Local File:
|
||||
@@ -221,8 +228,7 @@ class RagPipelineVariableApi(Resource):
|
||||
draft_var_srv = WorkflowDraftVariableService(
|
||||
session=db.session(),
|
||||
)
|
||||
payload = WorkflowDraftVariablePatchPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
variable_id_str = str(variable_id)
|
||||
variable = draft_var_srv.get_variable(variable_id=variable_id_str)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -17,6 +16,7 @@ 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
|
||||
def post(self, current_user: Account) -> JsonResponseWithStatus:
|
||||
@model_validate(RagPipelineImportPayload)
|
||||
def post(self, req_data: RagPipelineImportPayload, 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=payload.mode,
|
||||
yaml_content=payload.yaml_content,
|
||||
yaml_url=payload.yaml_url,
|
||||
pipeline_id=payload.pipeline_id,
|
||||
dataset_name=payload.name,
|
||||
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,
|
||||
)
|
||||
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)
|
||||
def get(self, pipeline: Pipeline) -> JsonResponseWithStatus:
|
||||
@model_validate(IncludeSecretQuery)
|
||||
def get(self, req_data: IncludeSecretQuery, 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=query.include_secret == "true"
|
||||
pipeline=pipeline, include_secret=req_data.include_secret == "true"
|
||||
)
|
||||
|
||||
return dump_response(SimpleDataResponse, {"data": result}), 200
|
||||
|
||||
@@ -33,6 +33,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
@@ -274,12 +275,12 @@ class RagPipelineDraftRunIterationNodeApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(NodeRunPayload)
|
||||
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow iteration node
|
||||
"""
|
||||
payload = NodeRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate_single_iteration(
|
||||
@@ -309,12 +310,12 @@ class RagPipelineDraftRunLoopNodeApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(NodeRunPayload)
|
||||
def post(self, req_data: NodeRunPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow loop node
|
||||
"""
|
||||
payload = NodeRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate_single_loop(
|
||||
@@ -344,13 +345,13 @@ class DraftRagPipelineRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
@model_validate(DraftWorkflowRunPayload)
|
||||
def post(self, req_data: DraftWorkflowRunPayload, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
"""
|
||||
Run draft workflow
|
||||
"""
|
||||
pipeline = load_rag_pipeline(session, str(pipeline_id))
|
||||
payload = DraftWorkflowRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump()
|
||||
args = req_data.model_dump()
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate(
|
||||
@@ -378,14 +379,14 @@ class PublishedRagPipelineRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
@model_validate(PublishedWorkflowRunPayload)
|
||||
def post(self, req_data: PublishedWorkflowRunPayload, session: Session, current_user: Account, pipeline_id: UUID):
|
||||
"""
|
||||
Run published workflow
|
||||
"""
|
||||
pipeline = load_rag_pipeline(session, str(pipeline_id))
|
||||
payload = PublishedWorkflowRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
streaming = payload.response_mode == "streaming"
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
streaming = req_data.response_mode == "streaming"
|
||||
|
||||
try:
|
||||
response = PipelineGenerateService.generate(
|
||||
@@ -393,7 +394,7 @@ class PublishedRagPipelineRunApi(Resource):
|
||||
pipeline=pipeline,
|
||||
user=current_user,
|
||||
args=args,
|
||||
invoke_from=InvokeFrom.DEBUGGER if payload.is_preview else InvokeFrom.PUBLISHED_PIPELINE,
|
||||
invoke_from=InvokeFrom.DEBUGGER if req_data.is_preview else InvokeFrom.PUBLISHED_PIPELINE,
|
||||
streaming=streaming,
|
||||
)
|
||||
|
||||
@@ -413,11 +414,11 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(DatasourceNodeRunPayload)
|
||||
def post(self, req_data: DatasourceNodeRunPayload, 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(
|
||||
@@ -425,11 +426,11 @@ class RagPipelinePublishedDatasourceNodeRunApi(Resource):
|
||||
rag_pipeline_service.run_datasource_workflow_node(
|
||||
pipeline=pipeline,
|
||||
node_id=node_id,
|
||||
user_inputs=payload.inputs,
|
||||
user_inputs=req_data.inputs,
|
||||
account=current_user,
|
||||
datasource_type=payload.datasource_type,
|
||||
datasource_type=req_data.datasource_type,
|
||||
is_published=False,
|
||||
credential_id=payload.credential_id,
|
||||
credential_id=req_data.credential_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -446,11 +447,11 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(DatasourceNodeRunPayload)
|
||||
def post(self, req_data: DatasourceNodeRunPayload, 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(
|
||||
@@ -458,11 +459,11 @@ class RagPipelineDraftDatasourceNodeRunApi(Resource):
|
||||
rag_pipeline_service.run_datasource_workflow_node(
|
||||
pipeline=pipeline,
|
||||
node_id=node_id,
|
||||
user_inputs=payload.inputs,
|
||||
user_inputs=req_data.inputs,
|
||||
account=current_user,
|
||||
datasource_type=payload.datasource_type,
|
||||
datasource_type=req_data.datasource_type,
|
||||
is_published=False,
|
||||
credential_id=payload.credential_id,
|
||||
credential_id=req_data.credential_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -483,12 +484,12 @@ class RagPipelineDraftNodeRunApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
def post(self, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
@model_validate(NodeRunRequiredPayload)
|
||||
def post(self, req_data: NodeRunRequiredPayload, current_user: Account, pipeline: Pipeline, node_id: str):
|
||||
"""
|
||||
Run draft workflow node
|
||||
"""
|
||||
payload = NodeRunRequiredPayload.model_validate(console_ns.payload or {})
|
||||
inputs = payload.inputs
|
||||
inputs = req_data.inputs
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
workflow_node_execution = rag_pipeline_service.run_draft_workflow_node(
|
||||
@@ -617,16 +618,16 @@ class DefaultRagPipelineBlockConfigApi(Resource):
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@get_rag_pipeline
|
||||
def get(self, pipeline: Pipeline, block_type: str):
|
||||
@model_validate(DefaultBlockConfigQuery)
|
||||
def get(self, req_data: DefaultBlockConfigQuery, pipeline: Pipeline, block_type: str):
|
||||
"""
|
||||
Get default block config
|
||||
"""
|
||||
query = DefaultBlockConfigQuery.model_validate(request.args.to_dict())
|
||||
|
||||
filters = None
|
||||
if query.q:
|
||||
if req_data.q:
|
||||
try:
|
||||
filters = json.loads(query.q)
|
||||
filters = json.loads(req_data.q)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("Invalid filters")
|
||||
|
||||
@@ -651,16 +652,16 @@ class PublishedAllRagPipelineApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
def get(self, current_user: Account, pipeline: Pipeline):
|
||||
@model_validate(WorkflowListQuery)
|
||||
def get(self, req_data: WorkflowListQuery, current_user: Account, pipeline: Pipeline):
|
||||
"""
|
||||
Get published workflows
|
||||
"""
|
||||
query = WorkflowListQuery.model_validate(request.args.to_dict())
|
||||
|
||||
page = query.page
|
||||
limit = query.limit
|
||||
user_id = query.user_id
|
||||
named_only = query.named_only
|
||||
page = req_data.page
|
||||
limit = req_data.limit
|
||||
user_id = req_data.user_id
|
||||
named_only = req_data.named_only
|
||||
|
||||
if user_id:
|
||||
if user_id != current_user.id:
|
||||
@@ -733,12 +734,12 @@ class RagPipelineByIdApi(Resource):
|
||||
@with_current_user
|
||||
@get_rag_pipeline
|
||||
@console_ns.expect(console_ns.models[WorkflowUpdatePayload.__name__])
|
||||
def patch(self, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||
@model_validate(WorkflowUpdatePayload)
|
||||
def patch(self, req_data: WorkflowUpdatePayload, current_user: Account, pipeline: Pipeline, workflow_id: str):
|
||||
"""
|
||||
Update workflow attributes
|
||||
"""
|
||||
payload = WorkflowUpdatePayload.model_validate(console_ns.payload or {})
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
update_data = req_data.model_dump(exclude_unset=True)
|
||||
|
||||
if not update_data:
|
||||
return {"message": "No valid fields to update"}, 400
|
||||
@@ -803,12 +804,12 @@ class PublishedRagPipelineSecondStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def get(self, pipeline: Pipeline):
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
"""
|
||||
Get second step parameters of rag pipeline
|
||||
"""
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
node_id = req_data.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 {
|
||||
@@ -826,12 +827,12 @@ class PublishedRagPipelineFirstStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def get(self, pipeline: Pipeline):
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
"""
|
||||
Get first step parameters of rag pipeline
|
||||
"""
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
node_id = req_data.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 {
|
||||
@@ -849,12 +850,12 @@ class DraftRagPipelineFirstStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def get(self, pipeline: Pipeline):
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
"""
|
||||
Get first step parameters of rag pipeline
|
||||
"""
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
node_id = req_data.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 {
|
||||
@@ -872,12 +873,12 @@ class DraftRagPipelineSecondStepApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def get(self, pipeline: Pipeline):
|
||||
@model_validate(NodeIdQuery)
|
||||
def get(self, req_data: NodeIdQuery, pipeline: Pipeline):
|
||||
"""
|
||||
Get second step parameters of rag pipeline
|
||||
"""
|
||||
query = NodeIdQuery.model_validate(request.args.to_dict())
|
||||
node_id = query.node_id
|
||||
node_id = req_data.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)
|
||||
@@ -1045,11 +1046,12 @@ class RagPipelineDatasourceVariableApi(Resource):
|
||||
@get_rag_pipeline
|
||||
@edit_permission_required
|
||||
@rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)
|
||||
def post(self, current_user: Account, pipeline: Pipeline):
|
||||
@model_validate(DatasourceVariablesPayload)
|
||||
def post(self, req_data: DatasourceVariablesPayload, current_user: Account, pipeline: Pipeline):
|
||||
"""
|
||||
Set datasource variables
|
||||
"""
|
||||
args = DatasourceVariablesPayload.model_validate(console_ns.payload or {}).model_dump()
|
||||
args = req_data.model_dump()
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
workflow_node_execution = rag_pipeline_service.set_datasource_variables(
|
||||
@@ -1071,9 +1073,11 @@ class RagPipelineRecommendedPluginApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, current_user: Account):
|
||||
query = RagPipelineRecommendedPluginQuery.model_validate(request.args.to_dict())
|
||||
@model_validate(RagPipelineRecommendedPluginQuery)
|
||||
def get(self, req_data: RagPipelineRecommendedPluginQuery, current_tenant_id: str, current_user: Account):
|
||||
|
||||
rag_pipeline_service = RagPipelineService(db.session())
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(query.type, current_user, current_tenant_id)
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(
|
||||
req_data.type, current_user, current_tenant_id
|
||||
)
|
||||
return recommended_plugins
|
||||
|
||||
@@ -41,6 +41,7 @@ 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,
|
||||
@@ -332,9 +333,9 @@ class AccountAvatarApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
def get(self, current_user: Account):
|
||||
args = AccountAvatarQuery.model_validate(request.args.to_dict(flat=True))
|
||||
avatar = args.avatar
|
||||
@model_validate(AccountAvatarQuery)
|
||||
def get(self, req_data: AccountAvatarQuery, current_user: Account):
|
||||
avatar = req_data.avatar
|
||||
|
||||
if avatar.startswith(("http://", "https://")):
|
||||
return AvatarUrlResponse(avatar_url=avatar).model_dump(mode="json")
|
||||
|
||||
@@ -11,7 +11,6 @@ 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
|
||||
|
||||
@@ -294,14 +293,14 @@ class EndpointListApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, user_id: str):
|
||||
args = EndpointListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(EndpointListQuery)
|
||||
def get(self, req_data: EndpointListQuery, tenant_id: str, user_id: str):
|
||||
|
||||
endpoints = EndpointService.list_endpoints(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
page=args.page,
|
||||
page_size=args.page_size,
|
||||
page=req_data.page,
|
||||
page_size=req_data.page_size,
|
||||
)
|
||||
|
||||
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
|
||||
@@ -322,15 +321,15 @@ class EndpointListForSinglePluginApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, user_id: str):
|
||||
args = EndpointListForPluginQuery.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(EndpointListForPluginQuery)
|
||||
def get(self, req_data: EndpointListForPluginQuery, tenant_id: str, user_id: str):
|
||||
|
||||
endpoints = EndpointService.list_endpoints_for_single_plugin(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
plugin_id=args.plugin_id,
|
||||
page=args.page,
|
||||
page_size=args.page_size,
|
||||
plugin_id=req_data.plugin_id,
|
||||
page=req_data.page,
|
||||
page_size=req_data.page_size,
|
||||
)
|
||||
|
||||
return EndpointListResponse(endpoints=endpoints).model_dump(mode="json")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -18,6 +17,7 @@ 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
|
||||
def get(self, tenant_id: str):
|
||||
args = ParserGetDefault.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(ParserGetDefault)
|
||||
def get(self, req_data: ParserGetDefault, tenant_id: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
default_model_entity = model_provider_service.get_default_model_of_model_type(
|
||||
tenant_id=tenant_id, model_type=args.model_type
|
||||
tenant_id=tenant_id, model_type=req_data.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
|
||||
def post(self, tenant_id: str):
|
||||
args = ParserPostDefault.model_validate(console_ns.payload)
|
||||
@model_validate(ParserPostDefault)
|
||||
def post(self, req_data: ParserPostDefault, tenant_id: str):
|
||||
model_provider_service = ModelProviderService()
|
||||
model_settings = args.model_settings
|
||||
model_settings = req_data.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
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@model_validate(ParserPostModels)
|
||||
def post(self, req_data: ParserPostModels, tenant_id: str, provider: str):
|
||||
# To save the model's load balance configs
|
||||
args = ParserPostModels.model_validate(console_ns.payload)
|
||||
|
||||
if args.config_from == "custom-model":
|
||||
if not args.credential_id:
|
||||
if req_data.config_from == "custom-model":
|
||||
if not req_data.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=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.credential_id,
|
||||
)
|
||||
|
||||
model_load_balancing_service = ModelLoadBalancingService()
|
||||
|
||||
if args.load_balancing and args.load_balancing.configs:
|
||||
if req_data.load_balancing and req_data.load_balancing.configs:
|
||||
# save load balancing configs
|
||||
model_load_balancing_service.update_load_balancing_configs(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
configs=args.load_balancing.configs,
|
||||
config_from=args.config_from or "",
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
configs=req_data.load_balancing.configs,
|
||||
config_from=req_data.config_from or "",
|
||||
session=db.session(),
|
||||
)
|
||||
|
||||
if args.load_balancing.enabled:
|
||||
if req_data.load_balancing.enabled:
|
||||
model_load_balancing_service.enable_model_load_balancing(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
)
|
||||
else:
|
||||
model_load_balancing_service.disable_model_load_balancing(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.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
|
||||
def delete(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
@model_validate(ParserDeleteModels)
|
||||
def delete(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.remove_model(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.model_type
|
||||
)
|
||||
|
||||
return "", 204
|
||||
@@ -352,29 +352,29 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, user: Account, provider: str):
|
||||
args = ParserGetCredentials.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(ParserGetCredentials)
|
||||
def get(self, req_data: ParserGetCredentials, tenant_id: str, user: Account, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
current_credential = model_provider_service.get_model_credential(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.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=args.model,
|
||||
model_type=args.model_type,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
session=db.session(),
|
||||
config_from=args.config_from or "",
|
||||
config_from=req_data.config_from or "",
|
||||
)
|
||||
|
||||
if args.config_from == "predefined-model":
|
||||
if req_data.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=args.model_type,
|
||||
model=args.model,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.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
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
args = ParserCreateCredential.model_validate(console_ns.payload)
|
||||
@model_validate(ParserCreateCredential)
|
||||
def post(self, req_data: ParserCreateCredential, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
@@ -423,17 +423,17 @@ class ModelProviderModelCredentialApi(Resource):
|
||||
model_provider_service.create_model_credential(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
credentials=args.credentials,
|
||||
credential_name=args.name,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
credentials=req_data.credentials,
|
||||
credential_name=req_data.name,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
logger.exception(
|
||||
"Failed to save model credentials, tenant_id: %s, model: %s, model_type: %s",
|
||||
tenant_id,
|
||||
args.model,
|
||||
args.model_type,
|
||||
req_data.model,
|
||||
req_data.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
|
||||
def put(self, current_tenant_id: str, provider: str):
|
||||
args = ParserUpdateCredential.model_validate(console_ns.payload)
|
||||
@model_validate(ParserUpdateCredential)
|
||||
def put(self, req_data: ParserUpdateCredential, current_tenant_id: str, provider: str):
|
||||
|
||||
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=args.model_type,
|
||||
model=args.model,
|
||||
credentials=args.credentials,
|
||||
credential_id=args.credential_id,
|
||||
credential_name=args.name,
|
||||
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,
|
||||
)
|
||||
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
|
||||
def delete(self, current_tenant_id: str, provider: str):
|
||||
args = ParserDeleteCredential.model_validate(console_ns.payload)
|
||||
@model_validate(ParserDeleteCredential)
|
||||
def delete(self, req_data: ParserDeleteCredential, current_tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.remove_model_credential(
|
||||
tenant_id=current_tenant_id,
|
||||
provider=provider,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.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
|
||||
def post(self, current_tenant_id: str, provider: str):
|
||||
args = ParserSwitch.model_validate(console_ns.payload)
|
||||
@model_validate(ParserSwitch)
|
||||
def post(self, req_data: ParserSwitch, current_tenant_id: str, provider: str):
|
||||
|
||||
service = ModelProviderService()
|
||||
service.add_model_credential_to_model_list(
|
||||
tenant_id=current_tenant_id,
|
||||
provider=provider,
|
||||
model_type=args.model_type,
|
||||
model=args.model,
|
||||
credential_id=args.credential_id,
|
||||
model_type=req_data.model_type,
|
||||
model=req_data.model,
|
||||
credential_id=req_data.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)
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
@model_validate(ParserDeleteModels)
|
||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.enable_model(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.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)
|
||||
def patch(self, tenant_id: str, provider: str):
|
||||
args = ParserDeleteModels.model_validate(console_ns.payload)
|
||||
@model_validate(ParserDeleteModels)
|
||||
def patch(self, req_data: ParserDeleteModels, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
model_provider_service.disable_model(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.model, model_type=req_data.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
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
args = ParserValidate.model_validate(console_ns.payload)
|
||||
@model_validate(ParserValidate)
|
||||
def post(self, req_data: ParserValidate, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
|
||||
@@ -591,9 +591,9 @@ class ModelProviderModelValidateApi(Resource):
|
||||
model_provider_service.validate_model_credentials(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
model=args.model,
|
||||
model_type=args.model_type,
|
||||
credentials=args.credentials,
|
||||
model=req_data.model,
|
||||
model_type=req_data.model_type,
|
||||
credentials=req_data.credentials,
|
||||
)
|
||||
except CredentialsValidateFailedError as ex:
|
||||
result = False
|
||||
@@ -617,12 +617,12 @@ class ModelProviderModelParameterRuleApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, tenant_id: str, provider: str):
|
||||
args = ParserParameter.model_validate(request.args.to_dict(flat=True))
|
||||
@model_validate(ParserParameter)
|
||||
def get(self, req_data: ParserParameter, tenant_id: str, provider: str):
|
||||
|
||||
model_provider_service = ModelProviderService()
|
||||
parameter_rules = model_provider_service.get_model_parameter_rules(
|
||||
tenant_id=tenant_id, provider=provider, model=args.model
|
||||
tenant_id=tenant_id, provider=provider, model=req_data.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, rbac_permission_required
|
||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, 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__])
|
||||
def get(self):
|
||||
@model_validate(_RolesListQuery)
|
||||
def get(self, req_data: _RolesListQuery):
|
||||
tenant_id, account_id = _current_ids()
|
||||
query = _RolesListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
options = query.to_inner_options()
|
||||
options = req_data.to_inner_options()
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
result = _legacy_workspace_roles(
|
||||
options, include_owner=query.include_owner, billing_enabled=dify_config.BILLING_ENABLED
|
||||
options, include_owner=req_data.include_owner, billing_enabled=dify_config.BILLING_ENABLED
|
||||
)
|
||||
else:
|
||||
result = svc.RBACService.Roles.list(
|
||||
tenant_id,
|
||||
account_id,
|
||||
include_owner=query.include_owner,
|
||||
include_owner=req_data.include_owner,
|
||||
biiling_enabled=dify_config.BILLING_ENABLED,
|
||||
options=options,
|
||||
)
|
||||
|
||||
@@ -39,6 +39,7 @@ 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,
|
||||
@@ -233,13 +234,12 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, user: Account, provider: str):
|
||||
@model_validate(TriggerSubscriptionBuilderCreatePayload)
|
||||
def post(self, req_data: TriggerSubscriptionBuilderCreatePayload, 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(payload.credential_type)
|
||||
credential_type = CredentialType.of(req_data.credential_type)
|
||||
subscription_builder = TriggerSubscriptionBuilderService.create_trigger_subscription_builder(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user.id,
|
||||
@@ -298,11 +298,17 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderVerifyPayload,
|
||||
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(
|
||||
@@ -311,7 +317,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
credentials=payload.credentials,
|
||||
credentials=req_data.credentials,
|
||||
),
|
||||
)
|
||||
return dump_response(TriggerVerificationResponse, result)
|
||||
@@ -337,10 +343,17 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderUpdatePayload,
|
||||
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,
|
||||
@@ -348,10 +361,10 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
name=payload.name,
|
||||
parameters=payload.parameters,
|
||||
properties=payload.properties,
|
||||
credentials=payload.credentials,
|
||||
name=req_data.name,
|
||||
parameters=req_data.parameters,
|
||||
properties=req_data.properties,
|
||||
credentials=req_data.credentials,
|
||||
),
|
||||
).model_dump(mode="json")
|
||||
except Exception as e:
|
||||
@@ -406,9 +419,16 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderUpdatePayload,
|
||||
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(
|
||||
@@ -417,9 +437,9 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
|
||||
provider_id=TriggerProviderID(provider),
|
||||
subscription_builder_id=subscription_builder_id,
|
||||
subscription_builder_updater=SubscriptionBuilderUpdater(
|
||||
name=payload.name,
|
||||
parameters=payload.parameters,
|
||||
properties=payload.properties,
|
||||
name=req_data.name,
|
||||
parameters=req_data.parameters,
|
||||
properties=req_data.properties,
|
||||
),
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
@@ -442,11 +462,10 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, subscription_id: str):
|
||||
@model_validate(TriggerSubscriptionBuilderUpdatePayload)
|
||||
def post(self, req_data: TriggerSubscriptionBuilderUpdatePayload, 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,
|
||||
@@ -458,7 +477,9 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
|
||||
try:
|
||||
# For rename only, just update the name
|
||||
rename = request.name is not None and not any((request.credentials, request.parameters, request.properties))
|
||||
rename = req_data.name is not None and not any(
|
||||
(req_data.credentials, req_data.parameters, req_data.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)
|
||||
@@ -467,8 +488,8 @@ class TriggerSubscriptionUpdateApi(Resource):
|
||||
TriggerProviderService.update_trigger_subscription(
|
||||
tenant_id=tenant_id,
|
||||
subscription_id=subscription_id,
|
||||
name=request.name,
|
||||
properties=request.properties,
|
||||
name=req_data.name,
|
||||
properties=req_data.properties,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
|
||||
@@ -476,11 +497,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=request.name,
|
||||
name=req_data.name,
|
||||
provider_id=provider_id,
|
||||
subscription_id=subscription_id,
|
||||
credentials=request.credentials or subscription.credentials,
|
||||
parameters=request.parameters or subscription.parameters,
|
||||
credentials=req_data.credentials or subscription.credentials,
|
||||
parameters=req_data.parameters or subscription.parameters,
|
||||
)
|
||||
return SimpleResultResponse(result="success").model_dump(mode="json")
|
||||
except ValueError as e:
|
||||
@@ -740,18 +761,17 @@ class TriggerOAuthClientManageApi(Resource):
|
||||
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, provider: str):
|
||||
@model_validate(TriggerOAuthClientPayload)
|
||||
def post(self, req_data: TriggerOAuthClientPayload, 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=payload.client_params,
|
||||
enabled=payload.enabled,
|
||||
client_params=req_data.client_params,
|
||||
enabled=req_data.enabled,
|
||||
)
|
||||
return dump_response(SimpleResultResponse, result)
|
||||
|
||||
@@ -805,18 +825,24 @@ class TriggerSubscriptionVerifyApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, user: Account, provider: str, subscription_id: str):
|
||||
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: TriggerSubscriptionBuilderVerifyPayload,
|
||||
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=verify_request.credentials,
|
||||
credentials=req_data.credentials,
|
||||
)
|
||||
return dump_response(TriggerVerificationResponse, result)
|
||||
except ValueError as e:
|
||||
|
||||
@@ -538,8 +538,14 @@ class AgentModelResponseFormatConfig(AgentFlexibleConfig):
|
||||
type: str | None = Field(default=None, max_length=64)
|
||||
|
||||
|
||||
class AgentSoulModelSettings(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
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.
|
||||
"""
|
||||
|
||||
temperature: float | None = None
|
||||
top_p: float | None = None
|
||||
|
||||
@@ -14933,6 +14933,13 @@ 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
|
||||
from controllers.console.datasets.data_source import DataSourceNotionListApi, DataSourceNotionListQuery
|
||||
from models import Account
|
||||
from models.dataset import Document
|
||||
from models.enums import DataSourceType, DocumentCreatedFrom, IndexingStatus
|
||||
@@ -78,7 +78,11 @@ def test_notion_page_is_marked_bound_from_persisted_document(
|
||||
),
|
||||
):
|
||||
response, status = unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(), db_session_with_containers, tenant_id, account
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="c1", dataset_id=dataset_id),
|
||||
db_session_with_containers,
|
||||
tenant_id,
|
||||
account,
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -119,6 +119,29 @@ 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,6 +59,7 @@ 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
|
||||
@@ -144,7 +145,13 @@ class TestCompletionEndpoints:
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
):
|
||||
resp = method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
resp = method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
|
||||
assert resp == {"result": {"text": "ok"}}
|
||||
|
||||
@@ -167,7 +174,13 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
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
|
||||
@@ -186,7 +199,13 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(completion_module.ProviderNotInitializeError),
|
||||
):
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
|
||||
def test_completion_api_quota_exceeded(
|
||||
self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_engine: Engine
|
||||
@@ -205,7 +224,13 @@ class TestCompletionEndpoints:
|
||||
app.test_request_context("/", json={"inputs": {}, "model_config": {}, "query": "hi"}),
|
||||
pytest.raises(completion_module.ProviderQuotaExceededError),
|
||||
):
|
||||
method(api, session, _make_account(), app_model=MagicMock(id=APP_ID))
|
||||
method(
|
||||
api,
|
||||
CompletionMessagePayload(inputs={}, model_config={}, query="hi"),
|
||||
session,
|
||||
_make_account(),
|
||||
app_model=MagicMock(id=APP_ID),
|
||||
)
|
||||
|
||||
|
||||
class TestAppEndpoints:
|
||||
@@ -232,7 +257,17 @@ 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, unbound_session, app_model=_make_app(icon_type=app_module.IconType.EMOJI))
|
||||
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),
|
||||
)
|
||||
|
||||
assert response == {"id": "app-1"}
|
||||
assert app_service.update_app.call_args.args[1]["icon_type"] is None
|
||||
@@ -271,7 +306,16 @@ 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, unbound_session, app_model=_make_app())
|
||||
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(),
|
||||
)
|
||||
|
||||
assert response == {"id": "app-1"}
|
||||
assert app_service.update_app_icon.call_args.args[1:] == (
|
||||
@@ -378,7 +422,13 @@ 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, db.session, _make_account(), app_model=_make_app())
|
||||
result = method(
|
||||
api,
|
||||
AppSiteUpdatePayload(title="My Site", input_placeholder="Ask me anything"),
|
||||
db.session,
|
||||
_make_account(),
|
||||
app_model=_make_app(),
|
||||
)
|
||||
|
||||
db.session.refresh(site)
|
||||
assert isinstance(result, dict)
|
||||
@@ -445,7 +495,7 @@ class TestWorkflowAppLogEndpoints:
|
||||
)
|
||||
|
||||
with database_app.test_request_context("/?page=1&limit=20"):
|
||||
result = method(api, app_model=_make_app("app-1"))
|
||||
result = method(api, WorkflowAppLogQuery(page=1, limit=20), app_model=_make_app("app-1"))
|
||||
|
||||
assert result == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||
|
||||
@@ -475,7 +525,12 @@ class TestWorkflowDraftVariableEndpoints:
|
||||
monkeypatch.setattr(workflow_draft_variable_module, "WorkflowService", DummyWorkflowService)
|
||||
|
||||
with database_app.test_request_context("/?page=1&limit=20"):
|
||||
result = method(api, _make_account(), app_model=_make_app("app-1"))
|
||||
result = method(
|
||||
api,
|
||||
WorkflowDraftVariableListQuery(page=1, limit=20),
|
||||
_make_account(),
|
||||
app_model=_make_app("app-1"),
|
||||
)
|
||||
|
||||
assert result == {"items": [], "total": 0}
|
||||
|
||||
@@ -528,7 +583,16 @@ class TestWorkflowDraftVariableEndpoints:
|
||||
"deleted_environment_variable_ids": ["env-b"],
|
||||
},
|
||||
):
|
||||
result = method(api, _make_account(), app_model=_make_app())
|
||||
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(),
|
||||
)
|
||||
|
||||
assert result == {"result": "success"}
|
||||
assert [(variable.id, variable.value) for variable in captured["environment_variables"]] == [("env-a", "new-a")]
|
||||
@@ -574,7 +638,7 @@ class TestWorkflowStatisticEndpoints:
|
||||
with database_app.test_request_context("/"):
|
||||
account = _make_account()
|
||||
account.timezone = "UTC"
|
||||
response = method(api, account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
response = method(api, WorkflowStatisticQuery(), account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
|
||||
assert response.get_json() == {"data": [{"date": "2024-01-01"}]}
|
||||
|
||||
@@ -604,7 +668,7 @@ class TestWorkflowStatisticEndpoints:
|
||||
with database_app.test_request_context("/"):
|
||||
account = _make_account()
|
||||
account.timezone = "UTC"
|
||||
response = method(api, account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
response = method(api, WorkflowStatisticQuery(), account, app_model=_make_app("app-1", tenant_id="t1"))
|
||||
|
||||
assert response.get_json() == {"data": [{"date": "2024-01-02"}]}
|
||||
|
||||
@@ -634,7 +698,7 @@ class TestWorkflowTriggerEndpoints:
|
||||
db.session.commit()
|
||||
|
||||
with database_app.test_request_context("/?node_id=node-1"):
|
||||
result = method(api, app_model=_make_app())
|
||||
result = method(api, Parser(node_id="node-1"), 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, _make_account())
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _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, _make_account())
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _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, _make_account())
|
||||
response, status = method(api, app_import_module.AppImportPayload(mode="yaml-content"), _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()
|
||||
response, status = method(app_import_module.AppImportPayload(mode="yaml-content"))
|
||||
|
||||
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()
|
||||
response, status = method(app_import_module.AppImportPayload(mode="yaml-content", app_id="existing-app"))
|
||||
|
||||
assert transaction_events.commits == 1
|
||||
_assert_app_persistence(sqlite_app_engine, app_id, persisted=True)
|
||||
|
||||
@@ -331,19 +331,9 @@ 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, 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_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_app_partial_serialization_uses_aliases(app_models):
|
||||
@@ -650,7 +640,17 @@ def test_app_create_api_attaches_permission_keys(app, app_module, unbound_sessio
|
||||
replace_whitelist,
|
||||
)
|
||||
|
||||
resp, status = method(app_module.AppListApi(), unbound_session, "tenant-1", SimpleNamespace(id="acct-1"))
|
||||
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"),
|
||||
)
|
||||
|
||||
assert status == 201
|
||||
assert resp["permission_keys"] == ["app.acl.view_layout", "app.acl.edit"]
|
||||
@@ -1076,6 +1076,7 @@ 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,6 +17,8 @@ from controllers.console.app.audio import (
|
||||
ChatMessageAudioApi,
|
||||
ChatMessageTextApi,
|
||||
TextModesApi,
|
||||
TextToSpeechPayload,
|
||||
TextToSpeechVoiceQuery,
|
||||
)
|
||||
from controllers.console.app.error import (
|
||||
AppUnavailableError,
|
||||
@@ -290,7 +292,7 @@ def test_console_text_api_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -
|
||||
method="POST",
|
||||
json={"text": "hello", "voice": "v"},
|
||||
):
|
||||
response = handler(api, app_model=app_model)
|
||||
response = handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
|
||||
@@ -315,7 +317,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, app_model=app_model)
|
||||
response = handler(api, TextToSpeechPayload(text="hello", message_id="message-1"), app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
assert calls["message_ref"] == MessageRef(AppRef("tenant-1", "app-1"), "message-1", account_id="account-1")
|
||||
@@ -334,7 +336,7 @@ def test_console_text_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
json={"text": "hello"},
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
handler(api, app_model=app_model)
|
||||
handler(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
|
||||
|
||||
def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -346,7 +348,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, app_model=app_model)
|
||||
response = handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
|
||||
assert response == expected_voices
|
||||
|
||||
@@ -364,7 +366,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, app_model=app_model)
|
||||
handler(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
|
||||
|
||||
def test_audio_to_text_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -424,7 +426,7 @@ def test_text_to_audio_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> N
|
||||
method="POST",
|
||||
json={"text": "hello"},
|
||||
):
|
||||
response = method(api, app_model=app_model)
|
||||
response = method(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
|
||||
assert response == {"audio": "ok"}
|
||||
|
||||
@@ -443,7 +445,7 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc
|
||||
method="GET",
|
||||
query_string={"language": "en-US"},
|
||||
):
|
||||
response = method(api, app_model=app_model)
|
||||
response = method(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
|
||||
assert response == expected_voices
|
||||
|
||||
@@ -481,7 +483,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, app_model=app_model)
|
||||
response = method(api, TextToSpeechPayload(text="hello"), app_model=app_model)
|
||||
assert response == {"audio": "test"}
|
||||
|
||||
|
||||
@@ -501,5 +503,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, app_model=app_model)
|
||||
response = method(api, TextToSpeechVoiceQuery(language="en-US"), app_model=app_model)
|
||||
assert isinstance(response, list)
|
||||
|
||||
@@ -58,7 +58,13 @@ 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, unbound_session, account, app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
assert response == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||
|
||||
|
||||
@@ -77,7 +83,13 @@ 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, unbound_session, account, app_model=SimpleNamespace(id="app-1"))
|
||||
method(
|
||||
api,
|
||||
conversation_module.CompletionConversationQuery(),
|
||||
unbound_session,
|
||||
account,
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
|
||||
def test_chat_conversation_list_advanced_chat_calls_paginate(
|
||||
@@ -96,7 +108,11 @@ 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, unbound_session, account, app_model=SimpleNamespace(id="app-1", mode=AppMode.ADVANCED_CHAT)
|
||||
api,
|
||||
conversation_module.ChatConversationQuery(),
|
||||
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,7 +51,11 @@ def test_get_conversation_variables_returns_paginated_response(
|
||||
method="GET",
|
||||
query_string={"conversation_id": "conv-1"},
|
||||
):
|
||||
response = method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
conversation_variables_module.ConversationVariablesQuery(conversation_id="conv-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert response["page"] == 1
|
||||
assert response["limit"] == 100
|
||||
@@ -90,7 +94,11 @@ def test_get_conversation_variables_normalizes_value_type_and_value(
|
||||
method="GET",
|
||||
query_string={"conversation_id": "conv-1"},
|
||||
):
|
||||
response = method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
conversation_variables_module.ConversationVariablesQuery(conversation_id="conv-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert response["data"][0]["value_type"] == "number"
|
||||
assert response["data"][0]["value"] == "42"
|
||||
@@ -102,4 +110,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):
|
||||
method(api, app_model=SimpleNamespace(id="app-1"))
|
||||
conversation_variables_module.ConversationVariablesQuery.model_validate({})
|
||||
|
||||
@@ -6,11 +6,19 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from flask import Flask, request
|
||||
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
|
||||
|
||||
@@ -61,7 +69,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, "t1")
|
||||
response = method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert response == {"rules": []}
|
||||
|
||||
@@ -81,7 +89,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, "t1")
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [(App,)], indirect=True)
|
||||
@@ -100,7 +108,9 @@ def test_instruction_generate_app_not_found(app: Flask, sqlite_session: Session)
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "app app-1 not found"
|
||||
@@ -127,7 +137,9 @@ def test_instruction_generate_workflow_not_found(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "workflow app-1 not found"
|
||||
@@ -155,7 +167,9 @@ def test_instruction_generate_node_missing(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "node node-1 not found"
|
||||
@@ -188,7 +202,7 @@ def test_instruction_generate_code_node(app: Flask, monkeypatch: pytest.MonkeyPa
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response = method(api, sqlite_session, "t1")
|
||||
response = method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
|
||||
assert response == {"code": "x"}
|
||||
assert workflow_service.app_model is app_model
|
||||
@@ -218,7 +232,7 @@ def test_instruction_generate_legacy_modify(
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response = method(api, sqlite_session, "t1")
|
||||
response = method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
|
||||
assert response == {"instruction": "ok"}
|
||||
|
||||
@@ -238,7 +252,9 @@ def test_instruction_generate_incompatible_params(app: Flask, sqlite_session: Se
|
||||
"model_config": _model_config_payload(),
|
||||
},
|
||||
):
|
||||
response, status = method(api, sqlite_session, "t1")
|
||||
response, status = method(
|
||||
api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1"
|
||||
)
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "incompatible parameters"
|
||||
@@ -253,7 +269,7 @@ def test_instruction_template_prompt(app: Flask) -> None:
|
||||
method="POST",
|
||||
json={"type": "prompt"},
|
||||
):
|
||||
response = method(api)
|
||||
response = method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
|
||||
assert "data" in response
|
||||
|
||||
@@ -268,7 +284,7 @@ def test_instruction_template_invalid_type(app: Flask) -> None:
|
||||
json={"type": "unknown"},
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api)
|
||||
method(api, InstructionTemplatePayload.model_validate(request.get_json()))
|
||||
|
||||
|
||||
# ─ /workflow-generate ─────────────────────────────────────────────────────────
|
||||
@@ -331,7 +347,7 @@ def test_workflow_generate_returns_service_result(app: Flask, monkeypatch: pytes
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, "t1")
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert response == expected
|
||||
|
||||
@@ -361,7 +377,7 @@ def test_workflow_generate_maps_provider_token_error(app: Flask, monkeypatch: py
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -379,7 +395,7 @@ def test_workflow_generate_maps_quota_error(app: Flask, monkeypatch: pytest.Monk
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -397,7 +413,7 @@ def test_workflow_generate_maps_model_not_support_error(app: Flask, monkeypatch:
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -415,7 +431,7 @@ def test_workflow_generate_maps_invoke_error(app: Flask, monkeypatch: pytest.Mon
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_workflow_generate_accepts_advanced_chat_mode(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -442,7 +458,7 @@ def test_workflow_generate_accepts_advanced_chat_mode(app: Flask, monkeypatch: p
|
||||
method="POST",
|
||||
json=payload,
|
||||
):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert captured["mode"] == "advanced-chat"
|
||||
assert captured["instruction"] == "Summarize a URL"
|
||||
@@ -474,7 +490,7 @@ def test_workflow_generate_forwards_current_graph_for_refine(app: Flask, monkeyp
|
||||
method="POST",
|
||||
json=payload,
|
||||
):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert captured["current_graph"] == graph
|
||||
|
||||
@@ -501,7 +517,7 @@ def test_workflow_generate_current_graph_defaults_to_none(app: Flask, monkeypatc
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
method(api, "t1")
|
||||
method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert captured["current_graph"] is None
|
||||
|
||||
@@ -528,7 +544,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, "t1")
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert captured["mode"] == "auto"
|
||||
assert response["mode"] == "advanced-chat"
|
||||
@@ -608,7 +624,7 @@ def test_workflow_instruction_suggestions_route_returns_list(app: Flask, monkeyp
|
||||
method="POST",
|
||||
json={"mode": "workflow", "language": "French", "count": 3},
|
||||
):
|
||||
response = method(api, "t1")
|
||||
response = method(api, WorkflowInstructionSuggestionsPayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert response == {"suggestions": ["Summarize a URL", "Translate text"]}
|
||||
assert captured["mode"] == "workflow"
|
||||
@@ -632,7 +648,7 @@ def test_workflow_instruction_suggestions_route_empty_is_valid_200(app: Flask, m
|
||||
method="POST",
|
||||
json={"mode": "advanced-chat"},
|
||||
):
|
||||
response = method(api, "t1")
|
||||
response = method(api, WorkflowInstructionSuggestionsPayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert response == {"suggestions": []}
|
||||
|
||||
@@ -667,7 +683,7 @@ def test_workflow_generate_stream_emits_plan_then_result(app: Flask, monkeypatch
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, "t1")
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
assert response.mimetype == "text/event-stream"
|
||||
frames = _read_sse_frames(response)
|
||||
|
||||
@@ -694,7 +710,7 @@ def test_workflow_generate_stream_provider_error_emits_result_event(
|
||||
method="POST",
|
||||
json=_workflow_generate_payload(),
|
||||
):
|
||||
response = method(api, "t1")
|
||||
response = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
frames = _read_sse_frames(response)
|
||||
|
||||
assert len(frames) == 1
|
||||
@@ -711,7 +727,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, "t1")
|
||||
response, status = method(api, WorkflowGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
assert status == 400
|
||||
assert response["errors"][0]["code"] == "EMPTY_INSTRUCTION"
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from flask import Flask, request
|
||||
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
|
||||
|
||||
@@ -47,7 +53,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, "t1")
|
||||
method(api, RuleGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_rule_code_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -73,7 +79,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, "t1")
|
||||
method(api, RuleCodeGeneratePayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
def test_structured_output_generate_exceptions(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -100,7 +106,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, "t1")
|
||||
method(api, RuleStructuredOutputPayload.model_validate(request.get_json()), "t1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
|
||||
@@ -138,4 +144,4 @@ def test_instruction_generate_exceptions(
|
||||
},
|
||||
):
|
||||
with pytest.raises(expected_exception):
|
||||
method(api, sqlite_session, "t1")
|
||||
method(api, InstructionGeneratePayload.model_validate(request.get_json()), sqlite_session, "t1")
|
||||
|
||||
@@ -53,7 +53,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-01", "message_count": 3}]}
|
||||
|
||||
@@ -67,7 +72,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
|
||||
|
||||
@@ -81,7 +91,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 1
|
||||
@@ -99,7 +114,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-04", "terminal_count": 7}]}
|
||||
|
||||
@@ -126,7 +146,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
|
||||
def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -142,7 +167,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 3
|
||||
@@ -156,7 +186,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert _json_payload(response) == {"data": []}
|
||||
|
||||
@@ -175,7 +210,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
|
||||
|
||||
@@ -192,7 +232,12 @@ 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(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
|
||||
response = method(
|
||||
api,
|
||||
SimpleNamespace(start=None, end=None),
|
||||
SimpleNamespace(timezone="UTC"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
data = _json_payload(response)
|
||||
assert len(data["data"]) == 2
|
||||
|
||||
@@ -98,7 +98,11 @@ 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, app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"))
|
||||
payload = handler(
|
||||
api,
|
||||
workflow_run_module.WorkflowRunListQuery(limit=10),
|
||||
app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"),
|
||||
)
|
||||
|
||||
response = _serialize_200_response(api.get, payload)
|
||||
|
||||
@@ -139,7 +143,11 @@ 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, app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"))
|
||||
payload = handler(
|
||||
api,
|
||||
workflow_run_module.WorkflowRunListQuery(limit=1),
|
||||
app_model=SimpleNamespace(id="app-1", tenant_id="tenant-1"),
|
||||
)
|
||||
|
||||
response = _serialize_200_response(api.get, payload)
|
||||
|
||||
|
||||
@@ -142,7 +142,12 @@ 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, app_model.tenant_id, app_model)
|
||||
response = method(
|
||||
api,
|
||||
workflow_trigger_module.ParserEnable(trigger_id=trigger.id, enable_trigger=True),
|
||||
app_model.tenant_id,
|
||||
app_model,
|
||||
)
|
||||
|
||||
assert response["id"] == trigger.id
|
||||
assert response["status"] == "enabled"
|
||||
|
||||
@@ -14,9 +14,15 @@ 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
|
||||
@@ -405,7 +411,8 @@ class TestDatasourceAuth:
|
||||
return_value=None,
|
||||
) as add_api_key_provider,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceCredentialPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -431,7 +438,7 @@ class TestDatasourceAuth:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_get_success(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -462,7 +469,7 @@ class TestDatasourceAuth:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_get_empty_list(self, app: Flask):
|
||||
api = DatasourceAuth()
|
||||
@@ -499,7 +506,8 @@ class TestDatasourceAuthDeleteApi:
|
||||
return_value=None,
|
||||
) as remove_datasource_credentials,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceCredentialDeletePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -522,7 +530,7 @@ class TestDatasourceAuthDeleteApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceCredentialDeletePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceAuthUpdateApi:
|
||||
@@ -545,7 +553,8 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_datasource_credentials,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 201
|
||||
@@ -573,7 +582,8 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_mock,
|
||||
):
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
update_mock.assert_called_once()
|
||||
@@ -595,7 +605,8 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 201
|
||||
@@ -615,7 +626,8 @@ class TestDatasourceAuthUpdateApi:
|
||||
return_value=None,
|
||||
) as update_mock,
|
||||
):
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
req_data = DatasourceCredentialUpdatePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
update_mock.assert_called_once()
|
||||
@@ -716,7 +728,8 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
) as setup_custom_client,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -760,7 +773,8 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -783,7 +797,8 @@ class TestDatasourceAuthOauthCustomClient:
|
||||
return_value=None,
|
||||
) as setup_mock,
|
||||
):
|
||||
response, status = method(api, "tenant-1", "notion")
|
||||
req_data = DatasourceCustomClientPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", "notion")
|
||||
|
||||
assert response == _success_response()
|
||||
setup_mock.assert_called_once()
|
||||
@@ -808,7 +823,8 @@ class TestDatasourceAuthDefaultApi:
|
||||
return_value=None,
|
||||
) as set_default_datasource_provider,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceDefaultPayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -828,7 +844,7 @@ class TestDatasourceAuthDefaultApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceDefaultPayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
|
||||
class TestDatasourceUpdateProviderNameApi:
|
||||
@@ -847,7 +863,8 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
return_value=None,
|
||||
) as update_datasource_provider_name,
|
||||
):
|
||||
response, status = method(api, "tenant-1", _PROVIDER_ID)
|
||||
req_data = DatasourceUpdateNamePayload.model_validate(payload)
|
||||
response, status = method(api, req_data, "tenant-1", _PROVIDER_ID)
|
||||
|
||||
assert response == _success_response()
|
||||
assert status == 200
|
||||
@@ -871,7 +888,7 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
def test_update_name_missing_credential_id(self, app: Flask):
|
||||
api = DatasourceUpdateProviderNameApi()
|
||||
@@ -884,4 +901,4 @@ class TestDatasourceUpdateProviderNameApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", "notion")
|
||||
method(api, DatasourceUpdateNamePayload.model_validate(payload), "tenant-1", "notion")
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
@@ -49,7 +50,8 @@ class TestDataSourceContentPreviewApi:
|
||||
return_value=service_instance,
|
||||
),
|
||||
):
|
||||
response, status = method(api, account, pipeline, node_id)
|
||||
req_data = Parser.model_validate(payload)
|
||||
response, status = method(api, req_data, account, pipeline, node_id)
|
||||
|
||||
service_instance.run_datasource_node_preview.assert_called_once_with(
|
||||
pipeline=pipeline,
|
||||
@@ -80,7 +82,7 @@ class TestDataSourceContentPreviewApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, account, pipeline, "node-1")
|
||||
method(api, Parser.model_validate(payload), account, pipeline, "node-1")
|
||||
|
||||
def test_post_without_credential_id(self, app: Flask):
|
||||
api = DataSourceContentPreviewApi()
|
||||
@@ -106,7 +108,8 @@ class TestDataSourceContentPreviewApi:
|
||||
return_value=service_instance,
|
||||
),
|
||||
):
|
||||
response, status = method(api, account, pipeline, "node-1")
|
||||
req_data = Parser.model_validate(payload)
|
||||
response, status = method(api, req_data, account, pipeline, "node-1")
|
||||
|
||||
service_instance.run_datasource_node_preview.assert_called_once()
|
||||
assert status == 200
|
||||
|
||||
@@ -15,8 +15,11 @@ 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
|
||||
@@ -90,7 +93,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, session, tenant_id)
|
||||
response, status = method(api, PipelineTemplateListQuery(), session, tenant_id)
|
||||
|
||||
assert status == 200
|
||||
assert service_calls == [("built-in", "en-US", tenant_id)]
|
||||
@@ -120,7 +123,9 @@ 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, session, tenant_id)
|
||||
response, status = method(
|
||||
api, PipelineTemplateListQuery(type="customized", language="ja-JP"), session, tenant_id
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"pipeline_templates": []}
|
||||
@@ -147,7 +152,7 @@ class TestPipelineTemplateDetailApi:
|
||||
side_effect=get_pipeline_template_detail,
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "template-1")
|
||||
response, status = method(api, PipelineTemplateDetailQuery(type="customized"), session, "template-1")
|
||||
|
||||
assert status == 200
|
||||
assert response == {**_template_detail(), "created_by": None}
|
||||
@@ -170,7 +175,7 @@ class TestPipelineTemplateDetailApi:
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
method(api, session, "missing")
|
||||
method(api, PipelineTemplateDetailQuery(), session, "missing")
|
||||
|
||||
|
||||
class TestCustomizedPipelineTemplateApi:
|
||||
@@ -198,7 +203,9 @@ 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, tenant_id, account, "template-1")
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "template-1"
|
||||
)
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert len(service_calls) == 1
|
||||
@@ -242,7 +249,9 @@ 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, tenant_id, account, "template-1")
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "template-1"
|
||||
)
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert len(service_calls) == 1
|
||||
@@ -349,7 +358,9 @@ class TestPublishCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module, "RagPipelineService", Service),
|
||||
):
|
||||
response, status = method(api, tenant_id, account, "pipeline-1")
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "pipeline-1"
|
||||
)
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert service_calls == [("pipeline-1", payload, account, tenant_id)]
|
||||
@@ -386,7 +397,9 @@ class TestPublishCustomizedPipelineTemplateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
patch.object(module, "RagPipelineService", Service),
|
||||
):
|
||||
response, status = method(api, tenant_id, account, "pipeline-1")
|
||||
response, status = method(
|
||||
api, CustomizedPipelineTemplatePayload.model_validate(payload), tenant_id, account, "pipeline-1"
|
||||
)
|
||||
|
||||
assert (response, status) == ("", 204)
|
||||
assert service_calls == [
|
||||
|
||||
@@ -15,6 +15,7 @@ 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
|
||||
|
||||
@@ -50,7 +51,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
return_value=mock_service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, "tenant-1", user)
|
||||
response, status = method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
assert status == 201
|
||||
assert response == {
|
||||
@@ -75,7 +76,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
def test_post_dataset_name_duplicate(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -96,7 +97,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
def test_post_invalid_payload(self, app: Flask) -> None:
|
||||
api = CreateRagPipelineDatasetApi()
|
||||
@@ -110,7 +111,7 @@ class TestCreateRagPipelineDatasetApi:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "tenant-1", user)
|
||||
method(api, RagPipelineDatasetImportPayload.model_validate(payload), "tenant-1", user)
|
||||
|
||||
|
||||
class TestCreateEmptyRagPipelineDatasetApi:
|
||||
|
||||
@@ -8,12 +8,14 @@ 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
|
||||
@@ -72,7 +74,7 @@ class TestRagPipelineVariableCollectionApi:
|
||||
return_value=draft_srv,
|
||||
),
|
||||
):
|
||||
result = method(api, editor_user, pipeline)
|
||||
result = method(api, PaginationQuery(page=1, limit=10), editor_user, pipeline)
|
||||
|
||||
assert result is var_list
|
||||
draft_srv.list_variables_without_values.assert_called_once_with(
|
||||
@@ -100,7 +102,7 @@ class TestRagPipelineVariableCollectionApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DraftWorkflowNotExist):
|
||||
method(api, editor_user, pipeline)
|
||||
method(api, PaginationQuery(), editor_user, pipeline)
|
||||
|
||||
def test_delete_variables_success(self, app: Flask, fake_db, editor_user):
|
||||
api = RagPipelineVariableCollectionApi()
|
||||
@@ -198,7 +200,7 @@ class TestRagPipelineVariableApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
method(api, editor_user, pipeline, "v1")
|
||||
method(api, WorkflowDraftVariablePatchPayload.model_validate(payload), editor_user, pipeline, "v1")
|
||||
|
||||
def test_delete_variable_success(self, app: Flask, fake_db, editor_user):
|
||||
api = RagPipelineVariableApi()
|
||||
|
||||
@@ -11,10 +11,12 @@ 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
|
||||
@@ -67,7 +69,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, user)
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
|
||||
assert status == 200
|
||||
assert response == {
|
||||
@@ -105,7 +107,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, user)
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
|
||||
assert status == 400
|
||||
assert response["status"] == "failed"
|
||||
@@ -139,7 +141,7 @@ class TestRagPipelineImportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, user)
|
||||
response, status = method(api, RagPipelineImportPayload(mode="create"), user)
|
||||
|
||||
assert status == 202
|
||||
assert response["status"] == "pending"
|
||||
@@ -287,7 +289,7 @@ class TestRagPipelineExportApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, pipeline)
|
||||
response, status = method(api, IncludeSecretQuery(), pipeline)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"data": "yaml: data"}
|
||||
|
||||
@@ -16,6 +16,14 @@ 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
|
||||
@@ -133,7 +141,9 @@ 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, _account(), pipeline=pipeline)
|
||||
response = handler(
|
||||
api, WorkflowListQuery(page=1, limit=10, user_id="", named_only=False), _account(), pipeline=pipeline
|
||||
)
|
||||
|
||||
assert response["items"][0]["id"] == DEFAULT_WORKFLOW_ID
|
||||
assert response["page"] == 1
|
||||
@@ -157,6 +167,7 @@ def test_rag_pipeline_workflow_patch_serializes_response_model(
|
||||
):
|
||||
response = handler(
|
||||
api,
|
||||
WorkflowUpdatePayload.model_validate(payload),
|
||||
_account(),
|
||||
pipeline=_pipeline(),
|
||||
workflow_id=DEFAULT_WORKFLOW_ID,
|
||||
@@ -198,7 +209,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, _pipeline())
|
||||
response = handler(api, NodeIdQuery(node_id="node-1"), _pipeline())
|
||||
|
||||
assert response["variables"] == variables
|
||||
|
||||
@@ -215,7 +226,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, DEFAULT_WORKFLOW_TENANT_ID, _account())
|
||||
response = handler(api, RagPipelineRecommendedPluginQuery(type="tool"), DEFAULT_WORKFLOW_TENANT_ID, _account())
|
||||
|
||||
assert response == recommended_plugins
|
||||
|
||||
@@ -270,7 +281,12 @@ 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}),
|
||||
):
|
||||
response = handler(api, session, _account(), pipeline.id)
|
||||
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)
|
||||
|
||||
assert response == {"ok": True}
|
||||
load_pipeline.assert_called_once_with(session, pipeline.id)
|
||||
@@ -293,6 +309,11 @@ 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,
|
||||
@@ -301,4 +322,4 @@ def test_rag_pipeline_run_translates_rate_limit(
|
||||
patch.object(module.PipelineGenerateService, "generate", side_effect=InvokeRateLimitError("limit")),
|
||||
pytest.raises(InvokeRateLimitHttpError),
|
||||
):
|
||||
handler(api, session, _account(), pipeline.id)
|
||||
handler(api, req_data, session, _account(), pipeline.id)
|
||||
|
||||
@@ -22,8 +22,12 @@ 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,
|
||||
@@ -33,9 +37,12 @@ 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
|
||||
@@ -418,7 +425,7 @@ class TestDraftRunNodes:
|
||||
return_value={"ok": True},
|
||||
),
|
||||
):
|
||||
result = method(api, user, pipeline, "node")
|
||||
result = method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
assert result == {"ok": True}
|
||||
|
||||
def test_iteration_node_conversation_not_exists(self, app: Flask) -> None:
|
||||
@@ -436,7 +443,7 @@ class TestDraftRunNodes:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, user, pipeline, "node")
|
||||
method(api, NodeRunPayload(), user, pipeline, "node")
|
||||
|
||||
def test_loop_node_success(self, app: Flask) -> None:
|
||||
api = RagPipelineDraftRunLoopNodeApi()
|
||||
@@ -456,7 +463,7 @@ class TestDraftRunNodes:
|
||||
return_value={"ok": True},
|
||||
),
|
||||
):
|
||||
assert method(api, user, pipeline, "node") == {"ok": True}
|
||||
assert method(api, NodeRunPayload(), user, pipeline, "node") == {"ok": True}
|
||||
|
||||
|
||||
class TestDraftNodeRun:
|
||||
@@ -478,7 +485,7 @@ class TestDraftNodeRun:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, user, pipeline, "node")
|
||||
method(api, NodeRunRequiredPayload(inputs={}), user, pipeline, "node")
|
||||
|
||||
|
||||
class TestPublishedPipelineApis:
|
||||
@@ -551,7 +558,7 @@ class TestMiscApis:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, tenant_id, user)
|
||||
result = method(api, RagPipelineRecommendedPluginQuery(type="all"), tenant_id, user)
|
||||
assert result == recommended_plugins
|
||||
service.get_recommended_plugins.assert_called_once_with("all", user, tenant_id)
|
||||
|
||||
@@ -573,7 +580,7 @@ class TestDefaultBlockConfigApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, pipeline, "llm")
|
||||
result = method(api, DefaultBlockConfigQuery(q="{}"), pipeline, "llm")
|
||||
assert result == {"k": "v"}
|
||||
|
||||
def test_get_block_config_invalid_json(self, app: Flask) -> None:
|
||||
@@ -584,7 +591,7 @@ class TestDefaultBlockConfigApi:
|
||||
|
||||
with app.test_request_context("/?q=bad-json"):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, pipeline, "llm")
|
||||
method(api, DefaultBlockConfigQuery(q="bad-json"), pipeline, "llm")
|
||||
|
||||
|
||||
class TestPublishedAllRagPipelineApi:
|
||||
@@ -605,7 +612,7 @@ class TestPublishedAllRagPipelineApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, user, pipeline)
|
||||
result = method(api, WorkflowListQuery(), user, pipeline)
|
||||
|
||||
assert result["items"][0]["id"] == "w1"
|
||||
assert result["items"][0]["graph"] == {"nodes": [], "edges": []}
|
||||
@@ -622,7 +629,7 @@ class TestPublishedAllRagPipelineApi:
|
||||
app.test_request_context("/?user_id=u2"),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, user, pipeline)
|
||||
method(api, WorkflowListQuery(user_id="u2"), user, pipeline)
|
||||
|
||||
|
||||
class TestRagPipelineByIdApi:
|
||||
@@ -647,7 +654,7 @@ class TestRagPipelineByIdApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, user, pipeline, "w1")
|
||||
result = method(api, WorkflowUpdatePayload.model_validate(payload), user, pipeline, "w1")
|
||||
|
||||
assert result["id"] == "w1"
|
||||
assert result["marked_name"] == "test"
|
||||
@@ -661,7 +668,7 @@ class TestRagPipelineByIdApi:
|
||||
user = make_account()
|
||||
|
||||
with app.test_request_context("/", json={}):
|
||||
result, status = method(api, user, pipeline, "w1")
|
||||
result, status = method(api, WorkflowUpdatePayload(), user, pipeline, "w1")
|
||||
assert status == 400
|
||||
|
||||
def test_delete_success(self, app: Flask) -> None:
|
||||
@@ -797,6 +804,6 @@ class TestRagPipelineDatasourceVariableApi:
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
result = method(api, user, pipeline)
|
||||
result = method(api, DatasourceVariablesPayload.model_validate(payload), 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
|
||||
from controllers.console.datasets.data_source import DataSourceApi, DataSourceNotionListApi, DataSourceNotionListQuery
|
||||
from models import Account, DataSourceOauthBinding
|
||||
from models.engine import db
|
||||
|
||||
@@ -217,7 +217,11 @@ 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(), sqlite_session, "tenant-1", current_user
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1"),
|
||||
sqlite_session,
|
||||
"tenant-1",
|
||||
current_user,
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
@@ -255,7 +259,13 @@ 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(), sqlite_session, TENANT_ID, current_user)
|
||||
unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1"),
|
||||
sqlite_session,
|
||||
TENANT_ID,
|
||||
current_user,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
|
||||
@@ -276,4 +286,10 @@ 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(), sqlite_session, TENANT_ID, current_user)
|
||||
unwrap(DataSourceNotionListApi().get)(
|
||||
DataSourceNotionListApi(),
|
||||
DataSourceNotionListQuery(credential_id="credential-1", dataset_id="dataset-1"),
|
||||
sqlite_session,
|
||||
TENANT_ID,
|
||||
current_user,
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ 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
|
||||
@@ -45,7 +47,7 @@ class TestDataSourceNotionPreviewApi:
|
||||
return_value=extractor,
|
||||
),
|
||||
):
|
||||
response, status = method(api, "tenant-1", "p1", "page")
|
||||
response, status = method(api, DataSourceNotionPreviewQuery(credential_id="c1"), "tenant-1", "p1", "page")
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -80,7 +82,7 @@ class TestDataSourceNotionIndexingEstimateApi:
|
||||
return_value=MagicMock(model_dump=lambda: {"total_pages": 1}),
|
||||
),
|
||||
):
|
||||
response, status = method(api, sqlite_session, "tenant-1")
|
||||
response, status = method(api, NotionEstimatePayload.model_validate(payload), sqlite_session, "tenant-1")
|
||||
|
||||
assert status == 200
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from controllers.console.datasets.datasets import (
|
||||
DatasetApiDeleteApi,
|
||||
DatasetApiKeyApi,
|
||||
DatasetAutoDisableLogApi,
|
||||
DatasetCreatePayload,
|
||||
DatasetEnableApiApi,
|
||||
DatasetErrorDocs,
|
||||
DatasetIndexingEstimateApi,
|
||||
@@ -28,7 +29,9 @@ 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
|
||||
@@ -489,7 +492,7 @@ class TestDatasetListApiPost:
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
patch.object(DatasetService, "create_empty_dataset", return_value=dataset),
|
||||
):
|
||||
_, status = method(api, MagicMock(), "tenant-1", user)
|
||||
_, status = method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", user)
|
||||
assert status == 201
|
||||
|
||||
def test_post_forbidden(self, app: Flask):
|
||||
@@ -499,7 +502,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, MagicMock(), "tenant-1", user)
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -514,14 +517,14 @@ class TestDatasetListApiPost:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, MagicMock(), "tenant-1", user)
|
||||
method(api, DatasetCreatePayload(**payload), 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, MagicMock(), "tenant-1", make_account())
|
||||
method(api, DatasetCreatePayload(), MagicMock(), "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_indexing_technique(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -529,7 +532,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, MagicMock(), "tenant-1", make_account())
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", make_account())
|
||||
|
||||
def test_post_invalid_provider(self, app: Flask):
|
||||
api = DatasetListApi()
|
||||
@@ -537,7 +540,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, MagicMock(), "tenant-1", make_account())
|
||||
method(api, DatasetCreatePayload(**payload), MagicMock(), "tenant-1", make_account())
|
||||
|
||||
|
||||
class TestDatasetApiGet:
|
||||
@@ -692,7 +695,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, MagicMock(), tenant_id, user, dataset_id)
|
||||
result, status = method(api, DatasetUpdatePayload(), MagicMock(), tenant_id, user, dataset_id)
|
||||
assert status == 200
|
||||
assert result["partial_member_list"] == []
|
||||
|
||||
@@ -704,7 +707,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, MagicMock(), "tenant-1", make_account(), "missing")
|
||||
method(api, DatasetUpdatePayload(), MagicMock(), "tenant-1", make_account(), "missing")
|
||||
|
||||
def test_patch_permission_denied(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -719,7 +722,7 @@ class TestDatasetApiPatch:
|
||||
patch.object(DatasetPermissionService, "check_permission", side_effect=Forbidden("no permission")),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
|
||||
def test_patch_partial_members_update(self, app: Flask):
|
||||
api = DatasetApi()
|
||||
@@ -736,7 +739,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, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
result, _ = method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
assert result["partial_member_list"] == ["u1", "u2"]
|
||||
|
||||
def test_patch_clear_partial_members(self, app: Flask):
|
||||
@@ -754,7 +757,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, MagicMock(), "tenant", make_account(), dataset_id)
|
||||
result, _ = method(api, DatasetUpdatePayload(), MagicMock(), "tenant", make_account(), dataset_id)
|
||||
assert result["partial_member_list"] == []
|
||||
|
||||
|
||||
@@ -1014,7 +1017,12 @@ 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, session, "tenant-1")
|
||||
response, status = method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
assert status == 200
|
||||
assert response == {
|
||||
"tokens": 0,
|
||||
@@ -1036,7 +1044,12 @@ class TestDatasetIndexingEstimateApi:
|
||||
patch("controllers.console.datasets.datasets.DocumentService.estimate_args_validate", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1")
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_llm_bad_request_error(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1055,7 +1068,12 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, session, "tenant-1")
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1074,7 +1092,12 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, session, "tenant-1")
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
def test_post_generic_exception(self, app: Flask):
|
||||
api = DatasetIndexingEstimateApi()
|
||||
@@ -1092,7 +1115,12 @@ class TestDatasetIndexingEstimateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(IndexingEstimateError):
|
||||
method(api, session, "tenant-1")
|
||||
method(
|
||||
api,
|
||||
IndexingEstimatePayload(**payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetRelatedAppListApi:
|
||||
|
||||
@@ -9,9 +9,11 @@ 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,
|
||||
@@ -20,6 +22,8 @@ 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
|
||||
@@ -50,6 +54,7 @@ def _segment():
|
||||
status=SegmentStatus.COMPLETED,
|
||||
updated_by="u1",
|
||||
)
|
||||
|
||||
segment.id = "seg-1"
|
||||
segment.created_at = naive_utc_now()
|
||||
segment.updated_at = naive_utc_now()
|
||||
@@ -354,7 +359,9 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
response, status = method(
|
||||
api, SegmentCreatePayload(content="test content"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
assert status == 200
|
||||
assert response["data"]["id"] == "seg-1"
|
||||
|
||||
@@ -378,7 +385,9 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), MagicMock(), "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
|
||||
def test_post_provider_token_not_init(self, app: Flask):
|
||||
api = DatasetDocumentSegmentAddApi()
|
||||
@@ -400,7 +409,9 @@ class TestDatasetDocumentSegmentAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), MagicMock(), "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentUpdateApi:
|
||||
@@ -441,7 +452,9 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
response, status = method(
|
||||
api, SegmentUpdatePayload(content="test content"), session, "tenant-1", user, "ds-1", "doc-1", "seg-1"
|
||||
)
|
||||
assert status == 200
|
||||
assert "data" in response
|
||||
|
||||
@@ -467,7 +480,16 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_patch_segment_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -495,7 +517,16 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_patch_llm_bad_request(self, app: Flask):
|
||||
api = DatasetDocumentSegmentUpdateApi()
|
||||
@@ -525,7 +556,16 @@ class TestDatasetDocumentSegmentUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
method(
|
||||
api,
|
||||
SegmentUpdatePayload(content="test content"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetDocumentSegmentBatchImportApi:
|
||||
@@ -564,7 +604,9 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
assert status == 200
|
||||
assert response["job_status"] == "waiting"
|
||||
|
||||
@@ -581,7 +623,15 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
patch("controllers.console.datasets.datasets_segments.DatasetService.get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_document_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -599,7 +649,15 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
)
|
||||
|
||||
def test_post_upload_file_not_found(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -619,7 +677,9 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
|
||||
def test_post_invalid_file_type(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -641,7 +701,9 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
|
||||
def test_post_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -665,7 +727,9 @@ class TestDatasetDocumentSegmentBatchImportApi:
|
||||
"controllers.console.datasets.datasets_segments.redis_client.setnx", side_effect=Exception("redis down")
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
assert status == 500
|
||||
assert "error" in response
|
||||
|
||||
@@ -747,7 +811,9 @@ class TestChildChunkAddApi:
|
||||
return_value=child_chunk,
|
||||
),
|
||||
):
|
||||
response, status = method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
response, status = method(
|
||||
api, ChildChunkCreatePayload(content="child"), MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1"
|
||||
)
|
||||
assert status == 200
|
||||
assert response["data"]["id"] == "cc-1"
|
||||
|
||||
@@ -778,7 +844,16 @@ class TestChildChunkAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ChildChunkIndexingError):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
def test_post_permission_denied(self, app: Flask):
|
||||
api = ChildChunkAddApi()
|
||||
@@ -799,7 +874,16 @@ class TestChildChunkAddApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1")
|
||||
method(
|
||||
api,
|
||||
ChildChunkCreatePayload(content="child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
)
|
||||
|
||||
|
||||
class TestChildChunkUpdateApi:
|
||||
@@ -922,7 +1006,17 @@ class TestChildChunkUpdateApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1", "seg-1", "cc-1")
|
||||
method(
|
||||
api,
|
||||
ChildChunkUpdatePayload(content="updated child"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
user,
|
||||
"ds-1",
|
||||
"doc-1",
|
||||
"seg-1",
|
||||
"cc-1",
|
||||
)
|
||||
|
||||
|
||||
class TestSegmentListAdvancedCases:
|
||||
@@ -1047,7 +1141,9 @@ class TestSegmentOperationCases:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderTokenNotInitError):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, SegmentCreatePayload(content="test content"), 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"""
|
||||
@@ -1063,7 +1159,15 @@ class TestSegmentOperationCases:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api,
|
||||
BatchImportPayload(upload_file_id="test-file-id"),
|
||||
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"""
|
||||
@@ -1083,7 +1187,9 @@ class TestSegmentOperationCases:
|
||||
patch("controllers.console.datasets.datasets_segments.DocumentService.get_document", return_value=document),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
|
||||
def test_batch_import_with_async_task_failure(self, app: Flask):
|
||||
api = DatasetDocumentSegmentBatchImportApi()
|
||||
@@ -1122,7 +1228,9 @@ class TestSegmentOperationCases:
|
||||
side_effect=Exception("Task failed"),
|
||||
),
|
||||
):
|
||||
response, status = method(api, session, "tenant-1", user, "ds-1", "doc-1")
|
||||
response, status = method(
|
||||
api, BatchImportPayload(upload_file_id="test-file-id"), session, "tenant-1", user, "ds-1", "doc-1"
|
||||
)
|
||||
assert status == 500
|
||||
assert "error" in response
|
||||
|
||||
|
||||
@@ -12,10 +12,14 @@ 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
|
||||
@@ -174,7 +178,9 @@ class TestExternalApiTemplateListApi:
|
||||
return_value=([api_item], 3),
|
||||
) as get_external_knowledge_apis,
|
||||
):
|
||||
resp, status = method(api, session, "tenant-1")
|
||||
resp, status = method(
|
||||
api, ExternalApiTemplateListQuery(page=2, limit=1, keyword="vector"), session, "tenant-1"
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
assert resp == {
|
||||
@@ -213,7 +219,9 @@ class TestExternalApiTemplateListApi:
|
||||
return_value=created,
|
||||
) as create_external_knowledge_api,
|
||||
):
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
resp, status = method(
|
||||
api, ExternalKnowledgeApiPayload.model_validate(payload), session, "tenant-1", current_user
|
||||
)
|
||||
|
||||
assert status == 201
|
||||
assert resp == _external_api_dict("api-created")
|
||||
@@ -239,7 +247,7 @@ class TestExternalApiTemplateListApi:
|
||||
patch.object(ExternalDatasetService, "validate_api_list"),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
|
||||
def test_post_duplicate_name(self, app: Flask, current_user: Account):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -258,7 +266,7 @@ class TestExternalApiTemplateListApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalApiTemplateApi:
|
||||
@@ -325,7 +333,14 @@ class TestExternalApiTemplateApi:
|
||||
return_value=updated,
|
||||
) as update_external_knowledge_api,
|
||||
):
|
||||
resp, status = method(api, session, "tenant-1", current_user, "api-updated")
|
||||
resp, status = method(
|
||||
api,
|
||||
ExternalKnowledgeApiPayload.model_validate(payload),
|
||||
session,
|
||||
"tenant-1",
|
||||
current_user,
|
||||
"api-updated",
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
assert resp == _external_api_dict("api-updated")
|
||||
@@ -405,7 +420,9 @@ class TestExternalDatasetCreateApi:
|
||||
) as dataset_response_source,
|
||||
):
|
||||
session = MagicMock()
|
||||
resp, status = method(api, session, "tenant-1", current_user)
|
||||
resp, status = method(
|
||||
api, ExternalDatasetCreatePayload.model_validate(payload), session, "tenant-1", current_user
|
||||
)
|
||||
|
||||
assert status == 201
|
||||
assert resp == _expected_dataset_detail_payload()
|
||||
@@ -433,7 +450,7 @@ class TestExternalDatasetCreateApi:
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=payload),
|
||||
):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApi:
|
||||
@@ -450,7 +467,7 @@ class TestExternalKnowledgeHitTestingApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), current_user, "dataset-id")
|
||||
method(api, ExternalHitTestingPayload(query="test"), MagicMock(), current_user, "dataset-id")
|
||||
|
||||
def test_hit_testing_success(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
@@ -496,7 +513,7 @@ class TestExternalKnowledgeHitTestingApi:
|
||||
) as external_retrieve,
|
||||
patch("controllers.console.datasets.external.dump_response", side_effect=lambda _model, value: value),
|
||||
):
|
||||
resp = method(api, session, current_user, "dataset-id")
|
||||
resp = method(api, ExternalHitTestingPayload.model_validate(payload), session, current_user, "dataset-id")
|
||||
|
||||
assert resp == retrieve_response
|
||||
check_dataset_permission.assert_called_once_with(dataset, current_user, session)
|
||||
@@ -549,7 +566,7 @@ class TestBedrockRetrievalApi:
|
||||
return_value=retrieval_response,
|
||||
) as knowledge_retrieval,
|
||||
):
|
||||
resp, status = method()
|
||||
resp, status = method(api, BedrockRetrievalPayload.model_validate(payload))
|
||||
|
||||
assert status == 200
|
||||
assert resp == retrieval_response
|
||||
@@ -576,7 +593,7 @@ class TestExternalApiTemplateListApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(DatasetNameDuplicateError):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
method(api, ExternalKnowledgeApiPayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
|
||||
def test_get_with_pagination(self, app: Flask):
|
||||
api = ExternalApiTemplateListApi()
|
||||
@@ -591,7 +608,7 @@ class TestExternalApiTemplateListApiAdvanced:
|
||||
return_value=(templates, 25),
|
||||
) as get_external_knowledge_apis,
|
||||
):
|
||||
resp, status = method(api, MagicMock(), "tenant-1")
|
||||
resp, status = method(api, ExternalApiTemplateListQuery(page=2, limit=3), MagicMock(), "tenant-1")
|
||||
|
||||
assert status == 200
|
||||
assert resp == {
|
||||
@@ -621,7 +638,7 @@ class TestExternalDatasetCreateApiAdvanced:
|
||||
|
||||
with app.test_request_context("/", json=payload), patch.object(type(console_ns), "payload", payload):
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, MagicMock(), "tenant-1", current_user)
|
||||
method(api, ExternalDatasetCreatePayload.model_validate(payload), MagicMock(), "tenant-1", current_user)
|
||||
|
||||
|
||||
class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
@@ -644,7 +661,7 @@ class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), current_user, "ds-1")
|
||||
method(api, ExternalHitTestingPayload.model_validate(payload), MagicMock(), current_user, "ds-1")
|
||||
|
||||
def test_hit_testing_with_custom_retrieval_model(self, app: Flask, current_user: Account):
|
||||
api = ExternalKnowledgeHitTestingApi()
|
||||
@@ -682,7 +699,7 @@ class TestExternalKnowledgeHitTestingApiAdvanced:
|
||||
},
|
||||
) as external_retrieve,
|
||||
):
|
||||
resp = method(api, session, current_user, "ds-1")
|
||||
resp = method(api, ExternalHitTestingPayload.model_validate(payload), session, current_user, "ds-1")
|
||||
|
||||
assert resp == {
|
||||
"query": {"content": "test query"},
|
||||
@@ -727,4 +744,4 @@ class TestBedrockRetrievalApiAdvanced:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method()
|
||||
method(api, BedrockRetrievalPayload.model_validate(payload))
|
||||
|
||||
@@ -7,6 +7,7 @@ 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,
|
||||
@@ -24,6 +25,7 @@ from services.metadata_service import MetadataService
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask("test_dataset_metadata")
|
||||
|
||||
app.config["TESTING"] = True
|
||||
return app
|
||||
|
||||
@@ -76,7 +78,9 @@ class TestDatasetMetadataCreateApi:
|
||||
MetadataService, "create_metadata", return_value={"id": "m1", "type": "string", "name": "author"}
|
||||
),
|
||||
):
|
||||
result, status = method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
result, status = method(
|
||||
api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id
|
||||
)
|
||||
assert status == 201
|
||||
assert result["type"] == "string"
|
||||
assert result["name"] == "author"
|
||||
@@ -92,7 +96,9 @@ class TestDatasetMetadataCreateApi:
|
||||
patch.object(DatasetService, "get_dataset", return_value=None),
|
||||
):
|
||||
with pytest.raises(NotFound, match="Dataset not found"):
|
||||
method(api, MagicMock(), "tenant-1", current_user, dataset_id)
|
||||
method(
|
||||
api, MetadataArgs(type="string", name="author"), MagicMock(), "tenant-1", current_user, dataset_id
|
||||
)
|
||||
|
||||
|
||||
class TestDatasetMetadataGetApi:
|
||||
@@ -140,7 +146,15 @@ class TestDatasetMetadataApi:
|
||||
return_value={"id": "m1", "type": "string", "name": "updated-name"},
|
||||
),
|
||||
):
|
||||
result, status = method(api, MagicMock(), "tenant-1", current_user, dataset_id, metadata_id)
|
||||
result, status = method(
|
||||
api,
|
||||
MetadataUpdatePayload(name="updated-name"),
|
||||
MagicMock(),
|
||||
"tenant-1",
|
||||
current_user,
|
||||
dataset_id,
|
||||
metadata_id,
|
||||
)
|
||||
assert status == 200
|
||||
assert result["type"] == "string"
|
||||
assert result["name"] == "updated-name"
|
||||
@@ -201,9 +215,14 @@ 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, MagicMock(), current_user, dataset_id)
|
||||
result, status = method(
|
||||
api,
|
||||
MetadataOperationData(operation_data=[{"document_id": "doc-1", "metadata_list": []}]),
|
||||
MagicMock(),
|
||||
current_user,
|
||||
dataset_id,
|
||||
)
|
||||
assert status == 204
|
||||
assert result == ""
|
||||
|
||||
@@ -16,6 +16,7 @@ from controllers.console.auth.error import (
|
||||
from controllers.console.error import AccountInFreezeError
|
||||
from controllers.console.workspace.account import (
|
||||
AccountAvatarApi,
|
||||
AccountAvatarQuery,
|
||||
AccountDeleteApi,
|
||||
AccountDeleteVerifyApi,
|
||||
AccountInitApi,
|
||||
@@ -216,7 +217,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/example",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, user)
|
||||
result = method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
|
||||
assert result == {"avatar_url": "https://signed/example"}
|
||||
sign_mock.assert_called_once_with(upload_file_id=file_id)
|
||||
@@ -256,7 +257,7 @@ class TestAccountAvatarApiGet:
|
||||
) as sign_mock,
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, user)
|
||||
method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
|
||||
sign_mock.assert_not_called()
|
||||
|
||||
@@ -289,7 +290,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/example",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, user)
|
||||
result = method(api, AccountAvatarQuery(avatar=file_id), user)
|
||||
|
||||
assert result == {"avatar_url": "https://signed/example"}
|
||||
sign_mock.assert_called_once_with(upload_file_id=file_id)
|
||||
@@ -308,7 +309,7 @@ class TestAccountAvatarApiGet:
|
||||
return_value="https://signed/should-not-use",
|
||||
) as sign_mock,
|
||||
):
|
||||
result = method(api, user)
|
||||
result = method(api, AccountAvatarQuery(avatar=external), user)
|
||||
|
||||
assert result == {"avatar_url": external}
|
||||
sign_mock.assert_not_called()
|
||||
|
||||
@@ -17,7 +17,9 @@ from controllers.console.workspace.endpoint import (
|
||||
EndpointIdPayload,
|
||||
EndpointItemApi,
|
||||
EndpointListApi,
|
||||
EndpointListForPluginQuery,
|
||||
EndpointListForSinglePluginApi,
|
||||
EndpointListQuery,
|
||||
EndpointUpdatePayload,
|
||||
LegacyEndpointUpdatePayload,
|
||||
)
|
||||
@@ -146,7 +148,7 @@ class TestEndpointListApi:
|
||||
return_value=[endpoint_entity],
|
||||
),
|
||||
):
|
||||
result = method(api, "t1", "u1")
|
||||
result = method(api, EndpointListQuery(page=1, page_size=10), "t1", "u1")
|
||||
|
||||
endpoint = result["endpoints"][0]
|
||||
assert endpoint["id"] == "e1"
|
||||
@@ -180,7 +182,7 @@ class TestEndpointListApi:
|
||||
app.test_request_context("/?page=0&page_size=10"),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "t1", "u1")
|
||||
method(api, EndpointListQuery(page=0, page_size=10), "t1", "u1")
|
||||
|
||||
|
||||
class TestEndpointListForSinglePluginApi:
|
||||
@@ -195,7 +197,7 @@ class TestEndpointListForSinglePluginApi:
|
||||
return_value=[_endpoint_entity()],
|
||||
),
|
||||
):
|
||||
result = method(api, "t1", "u1")
|
||||
result = method(api, EndpointListForPluginQuery(page=1, page_size=10, plugin_id="p1"), "t1", "u1")
|
||||
|
||||
assert result["endpoints"][0]["id"] == "e1"
|
||||
assert result["endpoints"][0]["settings"]["api_key"] == "pl********et"
|
||||
@@ -209,7 +211,7 @@ class TestEndpointListForSinglePluginApi:
|
||||
app.test_request_context("/?page=1&page_size=10"),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "t1", "u1")
|
||||
method(api, EndpointListForPluginQuery(page=1, page_size=10), "t1", "u1")
|
||||
|
||||
|
||||
class TestEndpointItemApi:
|
||||
|
||||
@@ -15,6 +15,16 @@ 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
|
||||
@@ -43,7 +53,7 @@ class TestDefaultModelApi:
|
||||
},
|
||||
}
|
||||
|
||||
result = method(api, "tenant1")
|
||||
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "tenant1")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -65,7 +75,7 @@ class TestDefaultModelApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, "tenant1")
|
||||
result = method(api, ParserPostDefault.model_validate(payload), "tenant1")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -79,7 +89,7 @@ class TestDefaultModelApi:
|
||||
):
|
||||
service.return_value.get_default_model_of_model_type.return_value = None
|
||||
|
||||
result = method(api, "t1")
|
||||
result = method(api, ParserGetDefault(model_type=ModelType.LLM), "t1")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -117,7 +127,7 @@ class TestModelProviderModelApi:
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
patch("controllers.console.workspace.models.ModelLoadBalancingService"),
|
||||
):
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
result, status = method(api, ParserPostModels.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert status == 200
|
||||
|
||||
@@ -134,7 +144,7 @@ class TestModelProviderModelApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
result, status = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert status == 204
|
||||
|
||||
@@ -177,7 +187,13 @@ 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, "tenant1", SimpleNamespace(id="u1"), "openai")
|
||||
result = method(
|
||||
api,
|
||||
ParserGetCredentials(model="gpt-4", model_type=ModelType.LLM),
|
||||
"tenant1",
|
||||
SimpleNamespace(id="u1"),
|
||||
"openai",
|
||||
)
|
||||
|
||||
assert "credentials" in result
|
||||
|
||||
@@ -195,7 +211,7 @@ class TestModelProviderModelCredentialApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, "tenant1", "openai")
|
||||
result, status = method(api, ParserCreateCredential.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert status == 201
|
||||
|
||||
@@ -212,7 +228,13 @@ 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, "t1", SimpleNamespace(id="u1"), "openai")
|
||||
result = method(
|
||||
api,
|
||||
ParserGetCredentials(model="gpt", model_type=ModelType.LLM),
|
||||
"t1",
|
||||
SimpleNamespace(id="u1"),
|
||||
"openai",
|
||||
)
|
||||
|
||||
assert result["credentials"] == {}
|
||||
|
||||
@@ -230,7 +252,7 @@ class TestModelProviderModelCredentialApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result, status = method(api, "t1", "openai")
|
||||
result, status = method(api, ParserDeleteCredential.model_validate(payload), "t1", "openai")
|
||||
|
||||
assert status == 204
|
||||
|
||||
@@ -250,7 +272,7 @@ class TestModelProviderModelCredentialSwitchApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserSwitch.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -269,7 +291,7 @@ class TestModelEnableDisableApis:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -286,7 +308,7 @@ class TestModelEnableDisableApis:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserDeleteModels.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -306,7 +328,7 @@ class TestModelProviderModelValidateApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch("controllers.console.workspace.models.ModelProviderService"),
|
||||
):
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "success"
|
||||
|
||||
@@ -327,7 +349,7 @@ class TestModelProviderModelValidateApi:
|
||||
):
|
||||
service_mock.return_value.validate_model_credentials.side_effect = CredentialsValidateFailedError("invalid")
|
||||
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserValidate.model_validate(payload), "tenant1", "openai")
|
||||
|
||||
assert result["result"] == "error"
|
||||
|
||||
@@ -343,7 +365,7 @@ class TestParameterAndAvailableModels:
|
||||
):
|
||||
service_mock.return_value.get_model_parameter_rules.return_value = []
|
||||
|
||||
result = method(api, "tenant1", "openai")
|
||||
result = method(api, ParserParameter(model="gpt-4"), "tenant1", "openai")
|
||||
|
||||
assert "data" in result
|
||||
|
||||
@@ -371,7 +393,7 @@ class TestParameterAndAvailableModels:
|
||||
):
|
||||
service.return_value.get_model_parameter_rules.return_value = []
|
||||
|
||||
result = method(api, "t1", "openai")
|
||||
result = method(api, ParserParameter(model="gpt"), "t1", "openai")
|
||||
|
||||
assert result["data"] == []
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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
|
||||
@@ -175,7 +176,10 @@ 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())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"page": 1, "limit": 2, "include_owner": 1}),
|
||||
)
|
||||
|
||||
owner_permission_keys = rbac_mod._LEGACY_ROLE_PERMISSION_KEYS["owner"]
|
||||
valid_owner_permission_keys = []
|
||||
@@ -230,7 +234,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())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" not in names
|
||||
@@ -242,7 +246,10 @@ 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())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"include_owner": 1}),
|
||||
)
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" in names
|
||||
@@ -254,7 +261,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())
|
||||
response = inspect.unwrap(rbac_mod.RBACRolesApi.get)(rbac_mod.RBACRolesApi(), _RolesListQuery())
|
||||
|
||||
names = [r["name"] for r in response["data"]]
|
||||
assert "owner" not in names
|
||||
@@ -267,7 +274,10 @@ 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())
|
||||
inspect.unwrap(rbac_mod.RBACRolesApi.get)(
|
||||
rbac_mod.RBACRolesApi(),
|
||||
_RolesListQuery.model_validate({"page": 2, "limit": 50, "reverse": True, "include_owner": 1}),
|
||||
)
|
||||
|
||||
_, kwargs = mock_list.call_args
|
||||
options = kwargs["options"]
|
||||
|
||||
@@ -15,15 +15,19 @@ 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,
|
||||
@@ -163,7 +167,13 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=subscription_builder(),
|
||||
),
|
||||
):
|
||||
result = method(api, "t1", mock_user(), "github")
|
||||
result = method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderCreatePayload(credential_type="UNAUTHORIZED"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
)
|
||||
assert result["subscription_builder"]["id"] == "b1"
|
||||
|
||||
def test_get_builder(self, app: Flask) -> None:
|
||||
@@ -196,7 +206,14 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value={"verified": True},
|
||||
),
|
||||
):
|
||||
assert method(api, "t1", mock_user(), "github", "b1") == {"verified": True}
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={"a": 1}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
) == {"verified": True}
|
||||
|
||||
def test_verify_builder_error(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionBuilderVerifyApi()
|
||||
@@ -210,7 +227,14 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, "t1", mock_user(), "github", "b1")
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
)
|
||||
|
||||
def test_update_builder(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionBuilderUpdateApi()
|
||||
@@ -223,7 +247,17 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=subscription_builder(),
|
||||
) as mock_update_builder,
|
||||
):
|
||||
assert method(api, "t1", mock_user(), "github", "b1")["id"] == "b1"
|
||||
assert (
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="n"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
)["id"]
|
||||
== "b1"
|
||||
)
|
||||
mock_update_builder.assert_called_once_with(
|
||||
tenant_id="t1",
|
||||
user_id="u1",
|
||||
@@ -263,7 +297,14 @@ class TestTriggerSubscriptionBuilderApis:
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
assert method(api, "t1", mock_user(), "github", "b1") == {"result": "success"}
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="x"),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"b1",
|
||||
) == {"result": "success"}
|
||||
|
||||
|
||||
class TestTriggerSubscriptionCrud:
|
||||
@@ -283,7 +324,12 @@ class TestTriggerSubscriptionCrud:
|
||||
),
|
||||
patch("controllers.console.workspace.trigger_providers.TriggerProviderService.update_trigger_subscription"),
|
||||
):
|
||||
assert method(api, "t1", "s1") == {"result": "success"}
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(name="x"),
|
||||
"t1",
|
||||
"s1",
|
||||
) == {"result": "success"}
|
||||
|
||||
def test_update_not_found(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionUpdateApi()
|
||||
@@ -297,7 +343,7 @@ class TestTriggerSubscriptionCrud:
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFoundError):
|
||||
method(api, "t1", "x")
|
||||
method(api, TriggerSubscriptionBuilderUpdatePayload(name="x"), "t1", "x")
|
||||
|
||||
def test_update_rebuild(self, app: Flask) -> None:
|
||||
api = TriggerSubscriptionUpdateApi()
|
||||
@@ -319,7 +365,12 @@ class TestTriggerSubscriptionCrud:
|
||||
"controllers.console.workspace.trigger_providers.TriggerProviderService.rebuild_trigger_subscription"
|
||||
),
|
||||
):
|
||||
assert method(api, "t1", "s1") == {"result": "success"}
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderUpdatePayload(credentials={}),
|
||||
"t1",
|
||||
"s1",
|
||||
) == {"result": "success"}
|
||||
|
||||
|
||||
class TestTriggerOAuthApis:
|
||||
@@ -499,7 +550,12 @@ class TestTriggerOAuthClientManageApi:
|
||||
return_value={"result": "success"},
|
||||
),
|
||||
):
|
||||
assert method(api, "t1", "github") == {"result": "success"}
|
||||
assert method(
|
||||
api,
|
||||
TriggerOAuthClientPayload(enabled=True),
|
||||
"t1",
|
||||
"github",
|
||||
) == {"result": "success"}
|
||||
|
||||
def test_delete_client(self, app: Flask) -> None:
|
||||
api = TriggerOAuthClientManageApi()
|
||||
@@ -526,7 +582,7 @@ class TestTriggerOAuthClientManageApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(api, "t1", "github")
|
||||
method(api, TriggerOAuthClientPayload(enabled=True), "t1", "github")
|
||||
|
||||
|
||||
class TestTriggerSubscriptionVerifyApi:
|
||||
@@ -541,7 +597,14 @@ class TestTriggerSubscriptionVerifyApi:
|
||||
return_value={"verified": True},
|
||||
),
|
||||
):
|
||||
assert method(api, "t1", mock_user(), "github", "s1") == {"verified": True}
|
||||
assert method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"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:
|
||||
@@ -556,4 +619,11 @@ class TestTriggerSubscriptionVerifyApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(BadRequest):
|
||||
method(api, "t1", mock_user(), "github", "s1")
|
||||
method(
|
||||
api,
|
||||
TriggerSubscriptionBuilderVerifyPayload(credentials={}),
|
||||
"t1",
|
||||
mock_user(),
|
||||
"github",
|
||||
"s1",
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import pytest
|
||||
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from models.agent_config_entities import (
|
||||
AgentSoulModelSettings,
|
||||
DeclaredArrayItem,
|
||||
DeclaredOutputChildConfig,
|
||||
DeclaredOutputConfig,
|
||||
@@ -9,6 +10,22 @@ 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")
|
||||
|
||||
|
||||
@@ -1586,6 +1586,7 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -2037,6 +2037,13 @@ 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,6 +2846,7 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -3643,6 +3643,13 @@ 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,6 +924,7 @@ export type AgentSoulModelSettings = {
|
||||
stop?: Array<string> | null
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type AgentSandboxProviderConfig = {
|
||||
|
||||
@@ -1155,6 +1155,13 @@ 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(),
|
||||
|
||||
@@ -22,15 +22,6 @@ vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({
|
||||
default: () => mockUseNodes(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/action-button', () => ({
|
||||
__esModule: true,
|
||||
default: ({ children, onClick }: { children?: ReactNode; onClick?: () => void }) => (
|
||||
<button type="button" aria-label="close-preview" onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/badge', () => ({
|
||||
__esModule: true,
|
||||
default: ({ children }: { children?: ReactNode }) => <div data-testid="badge">{children}</div>,
|
||||
@@ -140,7 +131,7 @@ describe('FormContentPreview', () => {
|
||||
<FormContentPreview content="content" formInputs={[]} userActions={[]} onClose={onClose} />,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'close-preview' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'operation.close' }))
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FormInputItem, UserAction } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import * as React from 'react'
|
||||
@@ -22,12 +21,12 @@ type FormContentPreviewProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const FormContentPreview: FC<FormContentPreviewProps> = ({
|
||||
const FormContentPreview = ({
|
||||
content,
|
||||
formInputs,
|
||||
userActions,
|
||||
onClose,
|
||||
}) => {
|
||||
}: FormContentPreviewProps) => {
|
||||
const { t } = useTranslation()
|
||||
const panelWidth = useStore((state) => state.panelWidth)
|
||||
const nodes = useNodes()
|
||||
@@ -69,8 +68,11 @@ const FormContentPreview: FC<FormContentPreviewProps> = ({
|
||||
<Badge uppercase className="border-text-accent-secondary text-text-accent-secondary">
|
||||
{t(($) => $[`${i18nPrefix}.formContent.preview`], { ns: 'workflow' })}
|
||||
</Badge>
|
||||
<ActionButton onClick={onClose}>
|
||||
<span className="i-ri-close-line size-5 text-text-tertiary" />
|
||||
<ActionButton
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onClose}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-5 text-text-tertiary" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className="max-h-[calc(100vh-167px)] overflow-y-auto px-4">
|
||||
|
||||
Reference in New Issue
Block a user