Files
dify/api/tests/unit_tests/controllers/console/app/test_completion.py
T

142 lines
5.3 KiB
Python

from __future__ import annotations
from collections.abc import Callable
from inspect import unwrap
from uuid import uuid4
import pytest
from flask import Flask
from flask.typing import ResponseReturnValue
from flask_restx import Resource
from sqlalchemy.orm import Session
from controllers.console.app import completion as completion_module
from core.app.app_config.easy_ui_based_app.model_config import manager as model_config_manager_module
from core.entities.model_entities import ModelStatus, ModelWithProviderEntity, SimpleModelProviderEntity
from core.entities.provider_configuration import ProviderConfigurations
from core.plugin.impl.model_runtime_factory import PluginModelAssembly
from enums import DeploymentEdition
from graphon.model_runtime.entities.common_entities import I18nObject
from graphon.model_runtime.entities.model_entities import FetchFrom, ModelPropertyKey, ModelType
from graphon.model_runtime.entities.provider_entities import ProviderEntity
from libs.external_api import ExternalApi
from models.model import AppMode, AppModelConfig
from tests.helpers.unit.model_factories import make_account, make_app
_TENANT_ID = "tenant-1"
_PROVIDER_ID = "langgenius/openai/openai"
_MODEL_ID = "gpt-4o-mini"
def _install_model_discovery(monkeypatch: pytest.MonkeyPatch) -> None:
"""Replace plugin-daemon I/O while retaining the production model validation objects."""
provider = ProviderEntity(
provider=_PROVIDER_ID,
label=I18nObject(en_US="OpenAI"),
supported_model_types=[ModelType.LLM],
configurate_methods=[],
)
model = ModelWithProviderEntity(
model=_MODEL_ID,
label=I18nObject(en_US="GPT-4o mini"),
model_type=ModelType.LLM,
fetch_from=FetchFrom.PREDEFINED_MODEL,
model_properties={ModelPropertyKey.MODE: "chat"},
status=ModelStatus.ACTIVE,
provider=SimpleModelProviderEntity(provider),
)
assembly = PluginModelAssembly(tenant_id=_TENANT_ID)
configurations = ProviderConfigurations(tenant_id=_TENANT_ID)
def get_providers() -> list[ProviderEntity]:
return [provider]
def get_configurations(tenant_id: str) -> ProviderConfigurations:
assert tenant_id == _TENANT_ID
return configurations
def get_models(
self: ProviderConfigurations, *, provider: str, model_type: ModelType
) -> list[ModelWithProviderEntity]:
assert self is configurations
assert provider == _PROVIDER_ID
assert model_type == ModelType.LLM
return [model]
def create_assembly(*, tenant_id: str) -> PluginModelAssembly:
assert tenant_id == _TENANT_ID
return assembly
monkeypatch.setattr(assembly.model_provider_factory, "get_providers", get_providers)
monkeypatch.setattr(assembly.provider_manager, "get_configurations", get_configurations)
monkeypatch.setattr(ProviderConfigurations, "get_models", get_models)
monkeypatch.setattr(model_config_manager_module, "create_plugin_model_assembly", create_assembly)
def test_chat_message_rejects_empty_agent_tool_as_invalid_param(
sqlite_session: Session,
monkeypatch: pytest.MonkeyPatch,
config_overrides: Callable[..., None],
) -> None:
"""The debugger HTTP path rejects malformed tools before generation starts."""
config_overrides(
DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY,
APP_DEFAULT_ACTIVE_REQUESTS=0,
APP_MAX_ACTIVE_REQUESTS=0,
)
_install_model_discovery(monkeypatch)
account = make_account(account_id=str(uuid4()))
app_id = str(uuid4())
stored_config = AppModelConfig(app_id=app_id, created_by=account.id, updated_by=account.id)
app_model = make_app(
app_id=app_id,
tenant_id=_TENANT_ID,
mode=AppMode.AGENT_CHAT,
app_model_config_id=stored_config.id,
max_active_requests=0,
created_by=account.id,
)
sqlite_session.add_all([stored_config, app_model])
sqlite_session.commit()
class ChatMessageHttpResource(Resource):
def post(self) -> ResponseReturnValue:
# Admission is supplied by this harness; parsing, generation validation,
# and HTTP error translation remain the real production path.
return unwrap(completion_module.ChatMessageApi.post)(
completion_module.ChatMessageApi(), sqlite_session, _TENANT_ID, account, app_model
)
http_app = Flask(__name__)
http_app.config.update(TESTING=True)
ExternalApi(http_app).add_resource(ChatMessageHttpResource, "/chat-messages")
response = http_app.test_client().post(
"/chat-messages",
json={
"inputs": {},
"query": "hello",
"response_mode": "streaming",
"model_config": {
"model": {
"provider": _PROVIDER_ID,
"name": _MODEL_ID,
"completion_params": {},
},
"agent_mode": {
"enabled": True,
"strategy": "router",
"tools": [{}],
},
},
},
)
assert response.status_code == 400
assert response.get_json() == {
"code": "invalid_param",
"message": "Each item in agent_mode.tools must be a non-empty object",
"status": 400,
}