mirror of
https://github.com/langgenius/dify.git
synced 2026-06-26 14:51:13 +08:00
fix(api): document message responses as serialized contracts
This commit is contained in:
parent
107344d243
commit
c7274677f4
@ -167,12 +167,16 @@ register_schema_models(
|
||||
ChatMessagesQuery,
|
||||
MessageFeedbackPayload,
|
||||
FeedbackExportQuery,
|
||||
)
|
||||
register_response_schema_models(
|
||||
console_ns,
|
||||
AnnotationCountResponse,
|
||||
SuggestedQuestionsResponse,
|
||||
MessageDetailResponse,
|
||||
MessageInfiniteScrollPaginationResponse,
|
||||
SimpleResultResponse,
|
||||
TextFileResponse,
|
||||
)
|
||||
register_response_schema_models(console_ns, SimpleResultResponse, TextFileResponse)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/chat-messages")
|
||||
|
||||
@ -13423,7 +13423,6 @@ Soft lifecycle state for Agent records.
|
||||
| created_at | integer | | No |
|
||||
| files | [ string ] | | Yes |
|
||||
| id | string | | Yes |
|
||||
| message_chain_id | string | | No |
|
||||
| message_id | string | | Yes |
|
||||
| observation | string | | No |
|
||||
| position | integer | | Yes |
|
||||
@ -14540,8 +14539,8 @@ Enum class for configurate method of provider model.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| annotation_create_account | [SimpleAccount](#simpleaccount) | | No |
|
||||
| annotation_id | string | | Yes |
|
||||
| created_at | integer | | No |
|
||||
| id | string | | Yes |
|
||||
|
||||
#### ConversationDetail
|
||||
|
||||
@ -17140,6 +17139,7 @@ Enum class for large language model mode.
|
||||
| agent_thoughts | [ [AgentThought](#agentthought) ] | | No |
|
||||
| annotation | [ConversationAnnotation](#conversationannotation) | | No |
|
||||
| annotation_hit_history | [ConversationAnnotationHitHistory](#conversationannotationhithistory) | | No |
|
||||
| answer | string | | Yes |
|
||||
| answer_tokens | integer | | No |
|
||||
| conversation_id | string | | Yes |
|
||||
| created_at | integer | | No |
|
||||
@ -17153,12 +17153,11 @@ Enum class for large language model mode.
|
||||
| inputs | object | | Yes |
|
||||
| message | [JSONValue](#jsonvalue) | | No |
|
||||
| message_files | [ [MessageFile](#messagefile) ] | | No |
|
||||
| message_metadata_dict | [JSONValue](#jsonvalue) | | No |
|
||||
| message_tokens | integer | | No |
|
||||
| metadata | [JSONValue](#jsonvalue) | | No |
|
||||
| parent_message_id | string | | No |
|
||||
| provider_response_latency | number | | No |
|
||||
| query | string | | Yes |
|
||||
| re_sign_file_url_answer | string | | Yes |
|
||||
| status | string | | Yes |
|
||||
| workflow_run_id | string | | No |
|
||||
|
||||
|
||||
@ -607,7 +607,11 @@ class TestMiscApis:
|
||||
method = unwrap(api.get)
|
||||
|
||||
service = MagicMock()
|
||||
service.get_recommended_plugins.return_value = [{"id": "p1"}]
|
||||
recommended_plugins = {
|
||||
"installed_recommended_plugins": [{"id": "p1"}],
|
||||
"uninstalled_recommended_plugins": [{"id": "p2"}],
|
||||
}
|
||||
service.get_recommended_plugins.return_value = recommended_plugins
|
||||
user = make_account()
|
||||
tenant_id = "tenant-1"
|
||||
|
||||
@ -619,7 +623,7 @@ class TestMiscApis:
|
||||
),
|
||||
):
|
||||
result = method(api, tenant_id, user)
|
||||
assert result == [{"id": "p1"}]
|
||||
assert result == recommended_plugins
|
||||
service.get_recommended_plugins.assert_called_once_with("all", user, tenant_id)
|
||||
|
||||
|
||||
@ -826,7 +830,7 @@ class TestRagPipelineByIdApi:
|
||||
result = method(api, pipeline, "old-workflow")
|
||||
|
||||
workflow_service.delete_workflow.assert_called_once()
|
||||
assert result == (None, 204)
|
||||
assert result == ("", 204)
|
||||
|
||||
def test_delete_active_workflow_rejected(self, app: Flask) -> None:
|
||||
api = RagPipelineByIdApi()
|
||||
|
||||
@ -77,9 +77,11 @@ def test_human_input_preview_delegates_to_service(
|
||||
|
||||
preview_payload = {
|
||||
"form_id": "node-42",
|
||||
"node_id": "node-42",
|
||||
"node_title": "Human Input",
|
||||
"form_content": "<div>example</div>",
|
||||
"inputs": [{"name": "topic"}],
|
||||
"actions": [{"id": "continue"}],
|
||||
"actions": [{"id": "continue", "title": "Continue"}],
|
||||
}
|
||||
service_instance = MagicMock()
|
||||
service_instance.get_human_input_form_preview.return_value = preview_payload
|
||||
@ -88,7 +90,15 @@ def test_human_input_preview_delegates_to_service(
|
||||
with app.test_request_context(case.path, method="POST", json={"inputs": {"topic": "tech"}}):
|
||||
response = case.resource_cls().post(app_id=app_model.id, node_id="node-42")
|
||||
|
||||
assert response == preview_payload
|
||||
assert response == {
|
||||
**preview_payload,
|
||||
"TYPE": "human_input_required",
|
||||
"actions": [{"id": "continue", "title": "Continue", "button_style": "default"}],
|
||||
"resolved_default_values": {},
|
||||
"display_in_ui": False,
|
||||
"form_token": None,
|
||||
"expiration_time": None,
|
||||
}
|
||||
service_instance.get_human_input_form_preview.assert_called_once_with(
|
||||
app_model=app_model,
|
||||
account=account,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from inspect import unwrap as inspect_unwrap
|
||||
from inspect import unwrap
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
@ -35,24 +35,16 @@ from models.model import AppMode
|
||||
from services.errors.conversation import ConversationNotExistsError
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
|
||||
unwrap: Any = inspect_unwrap
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account() -> Account:
|
||||
acc = Account(name="User", email="user@example.com")
|
||||
def account():
|
||||
acc = MagicMock(spec=Account)
|
||||
acc.id = "u1"
|
||||
return acc
|
||||
|
||||
|
||||
def _file_data() -> Any:
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
return file_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def trial_app_chat() -> MagicMock:
|
||||
def trial_app_chat():
|
||||
app = MagicMock()
|
||||
app.id = "a-chat"
|
||||
app.mode = AppMode.CHAT
|
||||
@ -60,7 +52,7 @@ def trial_app_chat() -> MagicMock:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def trial_app_completion() -> MagicMock:
|
||||
def trial_app_completion():
|
||||
app = MagicMock()
|
||||
app.id = "a-comp"
|
||||
app.mode = AppMode.COMPLETION
|
||||
@ -68,7 +60,7 @@ def trial_app_completion() -> MagicMock:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def trial_app_workflow() -> MagicMock:
|
||||
def trial_app_workflow():
|
||||
app = MagicMock()
|
||||
app.id = "a-workflow"
|
||||
app.mode = AppMode.WORKFLOW
|
||||
@ -76,7 +68,7 @@ def trial_app_workflow() -> MagicMock:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_parameters() -> dict[str, object]:
|
||||
def valid_parameters():
|
||||
return {
|
||||
"user_input_form": [],
|
||||
"system_parameters": {},
|
||||
@ -92,13 +84,54 @@ def valid_parameters() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def test_trial_workflow_uses_trial_scoped_simple_account_model() -> None:
|
||||
assert module.simple_account_model.name == "TrialSimpleAccount"
|
||||
assert hasattr(module.simple_account_model, "items")
|
||||
def test_trial_workflow_registers_normalized_simple_account_response_model():
|
||||
assert "SimpleAccountResponse" in module.console_ns.models
|
||||
|
||||
|
||||
def _response_model_name(entry: object) -> str:
|
||||
assert isinstance(entry, tuple)
|
||||
assert len(entry) >= 2
|
||||
model = entry[1]
|
||||
name = getattr(model, "name", None)
|
||||
assert isinstance(name, str)
|
||||
return name
|
||||
|
||||
|
||||
def test_trial_endpoints_keep_response_and_query_docs():
|
||||
untyped_generated_response_views = [
|
||||
module.TrialAppWorkflowRunApi.post,
|
||||
module.TrialChatApi.post,
|
||||
module.TrialCompletionApi.post,
|
||||
]
|
||||
for view in untyped_generated_response_views:
|
||||
apidoc = getattr(view, "__apidoc__", {})
|
||||
assert apidoc.get("responses", {})["200"] == ("Success", None, {})
|
||||
|
||||
cases = [
|
||||
(module.TrialMessageSuggestedQuestionApi.get, module.SuggestedQuestionsResponse.__name__),
|
||||
(module.TrialChatAudioApi.post, module.AudioTranscriptResponse.__name__),
|
||||
(module.TrialChatTextApi.post, module.AudioBinaryResponse.__name__),
|
||||
(module.TrialSitApi.get, module.SiteResponse.__name__),
|
||||
(module.TrialAppParameterApi.get, module.ParametersResponse.__name__),
|
||||
(module.AppApi.get, module.AppDetailWithSite.__name__),
|
||||
(module.AppWorkflowApi.get, module.WorkflowResponse.__name__),
|
||||
(module.DatasetListApi.get, module.TrialDatasetListResponse.__name__),
|
||||
]
|
||||
|
||||
for view, model_name in cases:
|
||||
apidoc = getattr(view, "__apidoc__", {})
|
||||
responses = apidoc.get("responses", {})
|
||||
assert _response_model_name(responses["200"]) == model_name
|
||||
|
||||
dataset_params = module.DatasetListApi.get.__apidoc__["params"]
|
||||
assert dataset_params["ids"]["in"] == "query"
|
||||
assert dataset_params["ids"]["type"] == "array"
|
||||
assert dataset_params["page"]["default"] == 1
|
||||
assert dataset_params["limit"]["default"] == 20
|
||||
|
||||
|
||||
class TestTrialAppWorkflowRunApi:
|
||||
def test_not_workflow_app(self, app: Flask, account: Account) -> None:
|
||||
def test_not_workflow_app(self, app: Flask, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -106,7 +139,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(NotWorkflowAppError):
|
||||
method(api, account, MagicMock(mode=AppMode.CHAT))
|
||||
|
||||
def test_success(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -119,7 +152,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
|
||||
assert result is not None
|
||||
|
||||
def test_workflow_provider_not_init(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_provider_not_init(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -134,7 +167,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_quota_exceeded(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_quota_exceeded(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -149,7 +182,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_model_not_support(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_model_not_support(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -164,7 +197,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_invoke_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_invoke_error(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -179,7 +212,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_rate_limit_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_rate_limit_error(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -194,7 +227,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_value_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_value_error(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -209,7 +242,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
with pytest.raises(ValueError):
|
||||
method(api, account, trial_app_workflow)
|
||||
|
||||
def test_workflow_generic_exception(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
def test_workflow_generic_exception(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -226,7 +259,7 @@ class TestTrialAppWorkflowRunApi:
|
||||
|
||||
|
||||
class TestTrialChatApi:
|
||||
def test_not_chat_app(self, app: Flask, account: Account) -> None:
|
||||
def test_not_chat_app(self, app: Flask, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -234,7 +267,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(NotChatAppError):
|
||||
method(api, account, MagicMock(mode="completion"))
|
||||
|
||||
def test_success(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -247,7 +280,7 @@ class TestTrialChatApi:
|
||||
|
||||
assert result is not None
|
||||
|
||||
def test_chat_conversation_not_exists(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_conversation_not_exists(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -262,7 +295,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(NotFound):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_conversation_completed(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_conversation_completed(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -277,7 +310,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(ConversationCompletedError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_app_config_broken(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_app_config_broken(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -292,7 +325,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_provider_not_init(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -307,7 +340,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_quota_exceeded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -322,7 +355,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_model_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_model_not_support(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -337,7 +370,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_invoke_error(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -352,7 +385,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_rate_limit_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_rate_limit_error(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -367,7 +400,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_value_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_value_error(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -382,7 +415,7 @@ class TestTrialChatApi:
|
||||
with pytest.raises(ValueError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_chat_generic_exception(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_chat_generic_exception(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -399,7 +432,7 @@ class TestTrialChatApi:
|
||||
|
||||
|
||||
class TestTrialCompletionApi:
|
||||
def test_not_completion_app(self, app: Flask, account: Account) -> None:
|
||||
def test_not_completion_app(self, app: Flask, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -407,7 +440,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(NotCompletionAppError):
|
||||
method(api, account, MagicMock(mode=AppMode.CHAT))
|
||||
|
||||
def test_success(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -420,7 +453,7 @@ class TestTrialCompletionApi:
|
||||
|
||||
assert result is not None
|
||||
|
||||
def test_completion_app_config_broken(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_app_config_broken(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -435,7 +468,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(AppUnavailableError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_provider_not_init(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_provider_not_init(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -450,7 +483,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_quota_exceeded(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_quota_exceeded(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -465,7 +498,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_model_not_support(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_model_not_support(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -480,7 +513,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_invoke_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_invoke_error(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -495,7 +528,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_rate_limit_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_rate_limit_error(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -510,7 +543,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_value_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_value_error(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -525,7 +558,7 @@ class TestTrialCompletionApi:
|
||||
with pytest.raises(ValueError):
|
||||
method(api, account, trial_app_completion)
|
||||
|
||||
def test_completion_generic_exception(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
def test_completion_generic_exception(self, app: Flask, trial_app_completion, account):
|
||||
api = module.TrialCompletionApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -542,7 +575,7 @@ class TestTrialCompletionApi:
|
||||
|
||||
|
||||
class TestTrialMessageSuggestedQuestionApi:
|
||||
def test_not_chat_app(self, app: Flask, account: Account) -> None:
|
||||
def test_not_chat_app(self, app: Flask, account):
|
||||
api = module.TrialMessageSuggestedQuestionApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -550,7 +583,7 @@ class TestTrialMessageSuggestedQuestionApi:
|
||||
with pytest.raises(NotChatAppError):
|
||||
method(api, account, MagicMock(mode="completion"), str(uuid4()))
|
||||
|
||||
def test_success(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialMessageSuggestedQuestionApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -566,7 +599,7 @@ class TestTrialMessageSuggestedQuestionApi:
|
||||
|
||||
assert result == {"data": ["q1", "q2"]}
|
||||
|
||||
def test_conversation_not_exists(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_conversation_not_exists(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialMessageSuggestedQuestionApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -583,14 +616,14 @@ class TestTrialMessageSuggestedQuestionApi:
|
||||
|
||||
|
||||
class TestTrialAppParameterApi:
|
||||
def test_app_unavailable(self) -> None:
|
||||
def test_app_unavailable(self):
|
||||
api = module.TrialAppParameterApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with pytest.raises(AppUnavailableError):
|
||||
method(api, None)
|
||||
|
||||
def test_success_non_workflow(self, valid_parameters: dict[str, object]) -> None:
|
||||
def test_success_non_workflow(self, valid_parameters):
|
||||
api = module.TrialAppParameterApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -617,11 +650,12 @@ class TestTrialAppParameterApi:
|
||||
|
||||
|
||||
class TestTrialChatAudioApi:
|
||||
def test_success(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -634,11 +668,12 @@ class TestTrialChatAudioApi:
|
||||
|
||||
assert result == {"text": "hello"}
|
||||
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -653,11 +688,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -672,11 +708,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(module.NoAudioUploadedError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_audio_too_large(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_audio_too_large(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -691,11 +728,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(module.AudioTooLargeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -710,11 +748,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(module.UnsupportedAudioTypeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_provider_not_support_tts(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_provider_not_support_tts(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -729,11 +768,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(module.ProviderNotSupportSpeechToTextError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -744,11 +784,12 @@ class TestTrialChatAudioApi:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -761,7 +802,7 @@ class TestTrialChatAudioApi:
|
||||
|
||||
|
||||
class TestTrialChatTextApi:
|
||||
def test_success(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_success(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -774,7 +815,7 @@ class TestTrialChatTextApi:
|
||||
|
||||
assert result == {"audio": "base64_data"}
|
||||
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -789,7 +830,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_provider_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_provider_not_support(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -804,7 +845,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(module.ProviderNotSupportSpeechToTextError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_audio_too_large(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_audio_too_large(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -819,7 +860,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(module.AudioTooLargeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -834,7 +875,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(module.NoAudioUploadedError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -845,7 +886,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -856,7 +897,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_model_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_model_not_support(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -867,7 +908,7 @@ class TestTrialChatTextApi:
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -880,7 +921,7 @@ class TestTrialChatTextApi:
|
||||
|
||||
|
||||
class TestTrialAppWorkflowTaskStopApi:
|
||||
def test_not_workflow_app(self, app: Flask, trial_app_chat: MagicMock) -> None:
|
||||
def test_not_workflow_app(self, app: Flask, trial_app_chat):
|
||||
api = module.TrialAppWorkflowTaskStopApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -888,7 +929,7 @@ class TestTrialAppWorkflowTaskStopApi:
|
||||
with pytest.raises(NotWorkflowAppError):
|
||||
method(api, trial_app_chat, str(uuid4()))
|
||||
|
||||
def test_success(self, app: Flask, trial_app_workflow: MagicMock) -> None:
|
||||
def test_success(self, app: Flask, trial_app_workflow, account):
|
||||
api = module.TrialAppWorkflowTaskStopApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -906,7 +947,7 @@ class TestTrialAppWorkflowTaskStopApi:
|
||||
|
||||
|
||||
class TestTrialSitApi:
|
||||
def test_no_site(self, app: Flask) -> None:
|
||||
def test_no_site(self, app: Flask):
|
||||
api = module.TrialSitApi()
|
||||
method = unwrap(api.get)
|
||||
app_model = MagicMock()
|
||||
@ -917,7 +958,7 @@ class TestTrialSitApi:
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, app_model)
|
||||
|
||||
def test_archived_tenant(self, app: Flask) -> None:
|
||||
def test_archived_tenant(self, app: Flask):
|
||||
api = module.TrialSitApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -932,7 +973,7 @@ class TestTrialSitApi:
|
||||
with pytest.raises(Forbidden):
|
||||
method(api, app_model)
|
||||
|
||||
def test_success(self, app: Flask) -> None:
|
||||
def test_success(self, app: Flask):
|
||||
api = module.TrialSitApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -957,11 +998,12 @@ class TestTrialSitApi:
|
||||
|
||||
|
||||
class TestTrialChatAudioApiExceptionHandlers:
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -976,11 +1018,12 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -995,11 +1038,12 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatAudioApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
file_data = _file_data()
|
||||
file_data: Any = BytesIO(b"fake audio data")
|
||||
file_data.filename = "test.wav"
|
||||
|
||||
with (
|
||||
app.test_request_context(
|
||||
@ -1016,7 +1060,7 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
|
||||
|
||||
class TestTrialChatTextApiExceptionHandlers:
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_app_config_broken(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
@ -1031,7 +1075,7 @@ class TestTrialChatTextApiExceptionHandlers:
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat, account):
|
||||
api = module.TrialChatTextApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
|
||||
@ -1,25 +1,36 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from controllers.service_api.app.workflow import WorkflowRunOutputsField, WorkflowRunStatusField
|
||||
from controllers.service_api.app.workflow import WorkflowRunResponse
|
||||
from graphon.enums import WorkflowExecutionStatus
|
||||
from libs.helper import dump_response
|
||||
from models.workflow import WorkflowRun
|
||||
|
||||
|
||||
def test_workflow_run_status_field_with_enum() -> None:
|
||||
field = WorkflowRunStatusField()
|
||||
obj = SimpleNamespace(status=WorkflowExecutionStatus.PAUSED)
|
||||
|
||||
assert field.output("status", obj) == "paused"
|
||||
def _workflow_run(status: WorkflowExecutionStatus, outputs: str | None = '{"foo": "bar"}') -> WorkflowRun:
|
||||
return WorkflowRun(
|
||||
id="run-id",
|
||||
workflow_id="workflow-id",
|
||||
status=status,
|
||||
inputs="{}",
|
||||
outputs=outputs,
|
||||
error=None,
|
||||
total_steps=1,
|
||||
total_tokens=2,
|
||||
elapsed_time=3.5,
|
||||
)
|
||||
|
||||
|
||||
def test_workflow_run_outputs_field_paused_returns_empty() -> None:
|
||||
field = WorkflowRunOutputsField()
|
||||
obj = SimpleNamespace(status=WorkflowExecutionStatus.PAUSED, outputs_dict={"foo": "bar"})
|
||||
def test_workflow_run_serializer_normalizes_status_enum() -> None:
|
||||
response = dump_response(WorkflowRunResponse, _workflow_run(WorkflowExecutionStatus.PAUSED))
|
||||
|
||||
assert field.output("outputs", obj) == {}
|
||||
assert response["status"] == "paused"
|
||||
|
||||
|
||||
def test_workflow_run_outputs_field_running_returns_outputs() -> None:
|
||||
field = WorkflowRunOutputsField()
|
||||
obj = SimpleNamespace(status=WorkflowExecutionStatus.RUNNING, outputs_dict={"foo": "bar"})
|
||||
def test_workflow_run_serializer_paused_returns_empty_outputs() -> None:
|
||||
response = dump_response(WorkflowRunResponse, _workflow_run(WorkflowExecutionStatus.PAUSED))
|
||||
|
||||
assert field.output("outputs", obj) == {"foo": "bar"}
|
||||
assert response["outputs"] == {}
|
||||
|
||||
|
||||
def test_workflow_run_serializer_running_returns_outputs() -> None:
|
||||
response = dump_response(WorkflowRunResponse, _workflow_run(WorkflowExecutionStatus.RUNNING))
|
||||
|
||||
assert response["outputs"] == {"foo": "bar"}
|
||||
|
||||
@ -325,10 +325,12 @@ class TestPipelineRunApiEntity:
|
||||
def test_entity_missing_required_field(self):
|
||||
"""Test entity raises on missing required field."""
|
||||
with pytest.raises(ValueError):
|
||||
PipelineRunApiEntity(
|
||||
inputs={},
|
||||
datasource_type="online_document",
|
||||
# missing datasource_info_list, start_node_id, etc.
|
||||
PipelineRunApiEntity.model_validate(
|
||||
{
|
||||
"inputs": {},
|
||||
"datasource_type": "online_document",
|
||||
# missing datasource_info_list, start_node_id, etc.
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -382,8 +384,19 @@ class TestDatasourcePluginsApiGet:
|
||||
mock_dataset = Mock()
|
||||
mock_db.session.scalar.return_value = mock_dataset
|
||||
|
||||
datasource_plugins = [
|
||||
{
|
||||
"node_id": "node-datasource-1",
|
||||
"plugin_id": "plugin-a",
|
||||
"provider_name": "provider-a",
|
||||
"datasource_type": "online_document",
|
||||
"title": "Online Docs",
|
||||
"user_input_variables": [{"variable": "url", "label": "URL", "type": "text-input", "required": True}],
|
||||
"credentials": [{"id": "cred-1", "name": "Default credential", "type": "oauth2", "is_default": True}],
|
||||
}
|
||||
]
|
||||
mock_svc_instance = Mock()
|
||||
mock_svc_instance.get_datasource_plugins.return_value = [{"name": "plugin_a"}]
|
||||
mock_svc_instance.get_datasource_plugins.return_value = datasource_plugins
|
||||
mock_svc_cls.return_value = mock_svc_instance
|
||||
|
||||
with app.test_request_context("/datasets/test/pipeline/datasource-plugins?is_published=true"):
|
||||
@ -391,11 +404,33 @@ class TestDatasourcePluginsApiGet:
|
||||
response, status = api.get(tenant_id=tenant_id, dataset_id=dataset_id)
|
||||
|
||||
assert status == 200
|
||||
assert response == [{"name": "plugin_a"}]
|
||||
assert response == datasource_plugins
|
||||
mock_svc_instance.get_datasource_plugins.assert_called_once_with(
|
||||
tenant_id=tenant_id, dataset_id=dataset_id, is_published=True
|
||||
)
|
||||
|
||||
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.db")
|
||||
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.RagPipelineService")
|
||||
def test_get_plugins_parses_false_is_published_query(self, mock_svc_cls, mock_db, app: Flask):
|
||||
"""Test false query string is parsed as boolean False."""
|
||||
tenant_id = str(uuid.uuid4())
|
||||
dataset_id = str(uuid.uuid4())
|
||||
|
||||
mock_db.session.scalar.return_value = Mock()
|
||||
mock_svc_instance = Mock()
|
||||
mock_svc_instance.get_datasource_plugins.return_value = []
|
||||
mock_svc_cls.return_value = mock_svc_instance
|
||||
|
||||
with app.test_request_context("/datasets/test/pipeline/datasource-plugins?is_published=false"):
|
||||
api = DatasourcePluginsApi()
|
||||
response, status = api.get(tenant_id=tenant_id, dataset_id=dataset_id)
|
||||
|
||||
assert status == 200
|
||||
assert response == []
|
||||
mock_svc_instance.get_datasource_plugins.assert_called_once_with(
|
||||
tenant_id=tenant_id, dataset_id=dataset_id, is_published=False
|
||||
)
|
||||
|
||||
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.db")
|
||||
def test_get_plugins_not_found(self, mock_db, app: Flask):
|
||||
"""Test NotFound when dataset check fails."""
|
||||
|
||||
@ -269,6 +269,7 @@ export type MessageDetailResponse = {
|
||||
agent_thoughts?: Array<AgentThought>
|
||||
annotation?: ConversationAnnotation | null
|
||||
annotation_hit_history?: ConversationAnnotationHitHistory | null
|
||||
answer: string
|
||||
answer_tokens?: number | null
|
||||
conversation_id: string
|
||||
created_at?: number | null
|
||||
@ -284,12 +285,11 @@ export type MessageDetailResponse = {
|
||||
}
|
||||
message?: JsonValue | null
|
||||
message_files?: Array<MessageFile>
|
||||
message_metadata_dict?: JsonValue | null
|
||||
message_tokens?: number | null
|
||||
metadata?: JsonValue | null
|
||||
parent_message_id?: string | null
|
||||
provider_response_latency?: number | null
|
||||
query: string
|
||||
re_sign_file_url_answer: string
|
||||
status: string
|
||||
workflow_run_id?: string | null
|
||||
}
|
||||
@ -732,7 +732,6 @@ export type AgentThought = {
|
||||
created_at?: number | null
|
||||
files: Array<string>
|
||||
id: string
|
||||
message_chain_id?: string | null
|
||||
message_id: string
|
||||
observation?: string | null
|
||||
position: number
|
||||
@ -752,8 +751,8 @@ export type ConversationAnnotation = {
|
||||
|
||||
export type ConversationAnnotationHitHistory = {
|
||||
annotation_create_account?: SimpleAccount | null
|
||||
annotation_id: string
|
||||
created_at?: number | null
|
||||
id: string
|
||||
}
|
||||
|
||||
export type HumanInputContent = {
|
||||
|
||||
@ -628,7 +628,6 @@ export const zAgentThought = z.object({
|
||||
created_at: z.int().nullish(),
|
||||
files: z.array(z.string()),
|
||||
id: z.string(),
|
||||
message_chain_id: z.string().nullish(),
|
||||
message_id: z.string(),
|
||||
observation: z.string().nullish(),
|
||||
position: z.int(),
|
||||
@ -1060,8 +1059,8 @@ export const zConversationAnnotation = z.object({
|
||||
*/
|
||||
export const zConversationAnnotationHitHistory = z.object({
|
||||
annotation_create_account: zSimpleAccount.nullish(),
|
||||
annotation_id: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
@ -2039,6 +2038,7 @@ export const zMessageDetailResponse = z.object({
|
||||
agent_thoughts: z.array(zAgentThought).optional(),
|
||||
annotation: zConversationAnnotation.nullish(),
|
||||
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
|
||||
answer: z.string(),
|
||||
answer_tokens: z.int().nullish(),
|
||||
conversation_id: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
@ -2052,12 +2052,11 @@ export const zMessageDetailResponse = z.object({
|
||||
inputs: z.record(z.string(), zJsonValue),
|
||||
message: zJsonValue.nullish(),
|
||||
message_files: z.array(zMessageFile).optional(),
|
||||
message_metadata_dict: zJsonValue.nullish(),
|
||||
message_tokens: z.int().nullish(),
|
||||
metadata: zJsonValue.nullish(),
|
||||
parent_message_id: z.string().nullish(),
|
||||
provider_response_latency: z.number().nullish(),
|
||||
query: z.string(),
|
||||
re_sign_file_url_answer: z.string(),
|
||||
status: z.string(),
|
||||
workflow_run_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
@ -418,6 +418,7 @@ export type MessageDetailResponse = {
|
||||
agent_thoughts?: Array<AgentThought>
|
||||
annotation?: ConversationAnnotation | null
|
||||
annotation_hit_history?: ConversationAnnotationHitHistory | null
|
||||
answer: string
|
||||
answer_tokens?: number | null
|
||||
conversation_id: string
|
||||
created_at?: number | null
|
||||
@ -433,12 +434,11 @@ export type MessageDetailResponse = {
|
||||
}
|
||||
message?: JsonValue | null
|
||||
message_files?: Array<MessageFile>
|
||||
message_metadata_dict?: JsonValue | null
|
||||
message_tokens?: number | null
|
||||
metadata?: JsonValue | null
|
||||
parent_message_id?: string | null
|
||||
provider_response_latency?: number | null
|
||||
query: string
|
||||
re_sign_file_url_answer: string
|
||||
status: string
|
||||
workflow_run_id?: string | null
|
||||
}
|
||||
@ -1425,7 +1425,6 @@ export type AgentThought = {
|
||||
created_at?: number | null
|
||||
files: Array<string>
|
||||
id: string
|
||||
message_chain_id?: string | null
|
||||
message_id: string
|
||||
observation?: string | null
|
||||
position: number
|
||||
@ -1445,8 +1444,8 @@ export type ConversationAnnotation = {
|
||||
|
||||
export type ConversationAnnotationHitHistory = {
|
||||
annotation_create_account?: SimpleAccount | null
|
||||
annotation_id: string
|
||||
created_at?: number | null
|
||||
id: string
|
||||
}
|
||||
|
||||
export type HumanInputContent = {
|
||||
|
||||
@ -1166,7 +1166,6 @@ export const zAgentThought = z.object({
|
||||
created_at: z.int().nullish(),
|
||||
files: z.array(z.string()),
|
||||
id: z.string(),
|
||||
message_chain_id: z.string().nullish(),
|
||||
message_id: z.string(),
|
||||
observation: z.string().nullish(),
|
||||
position: z.int(),
|
||||
@ -2269,8 +2268,8 @@ export const zConversationPagination = z.object({
|
||||
*/
|
||||
export const zConversationAnnotationHitHistory = z.object({
|
||||
annotation_create_account: zSimpleAccount.nullish(),
|
||||
annotation_id: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
@ -3384,6 +3383,7 @@ export const zMessageDetailResponse = z.object({
|
||||
agent_thoughts: z.array(zAgentThought).optional(),
|
||||
annotation: zConversationAnnotation.nullish(),
|
||||
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
|
||||
answer: z.string(),
|
||||
answer_tokens: z.int().nullish(),
|
||||
conversation_id: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
@ -3397,12 +3397,11 @@ export const zMessageDetailResponse = z.object({
|
||||
inputs: z.record(z.string(), zJsonValue),
|
||||
message: zJsonValue.nullish(),
|
||||
message_files: z.array(zMessageFile).optional(),
|
||||
message_metadata_dict: zJsonValue.nullish(),
|
||||
message_tokens: z.int().nullish(),
|
||||
metadata: zJsonValue.nullish(),
|
||||
parent_message_id: z.string().nullish(),
|
||||
provider_response_latency: z.number().nullish(),
|
||||
query: z.string(),
|
||||
re_sign_file_url_answer: z.string(),
|
||||
status: z.string(),
|
||||
workflow_run_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
@ -196,7 +196,6 @@ export type AgentThought = {
|
||||
created_at?: number | null
|
||||
files: Array<string>
|
||||
id: string
|
||||
message_chain_id?: string | null
|
||||
message_id: string
|
||||
observation?: string | null
|
||||
position: number
|
||||
|
||||
@ -210,7 +210,6 @@ export const zAgentThought = z.object({
|
||||
created_at: z.int().nullish(),
|
||||
files: z.array(z.string()),
|
||||
id: z.string(),
|
||||
message_chain_id: z.string().nullish(),
|
||||
message_id: z.string(),
|
||||
observation: z.string().nullish(),
|
||||
position: z.int(),
|
||||
|
||||
@ -217,14 +217,8 @@ const toFeedback = (feedback: NonNullable<MessageDetailResponse['feedbacks']>[nu
|
||||
}
|
||||
}
|
||||
|
||||
type AgentDebugMessageWithLegacyAnswer = MessageDetailResponse & {
|
||||
answer?: string | null
|
||||
}
|
||||
|
||||
const getAgentDebugMessageAnswer = (message: MessageDetailResponse) => {
|
||||
const legacyAnswer = (message as AgentDebugMessageWithLegacyAnswer).answer
|
||||
|
||||
return message.re_sign_file_url_answer ?? legacyAnswer ?? ''
|
||||
return message.answer ?? ''
|
||||
}
|
||||
|
||||
function getFormattedAgentDebugChatTree(messages: MessageDetailResponse[]): ChatItemInTree[] {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user