mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 22:31:24 +08:00
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Co-authored-by: Byron Wang <byron@dify.ai>
142 lines
5.3 KiB
Python
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,
|
|
}
|