Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from collections.abc import Callable, Iterator
from dataclasses import dataclass, field
from typing import Literal
from unittest.mock import patch

import httpx
Expand Down Expand Up @@ -93,7 +94,7 @@ def _tts_schema(audio_type: str) -> AIModelEntity:
)


def test_system_model_helpers_forward_user_id():
def test_system_model_helpers_forward_user_id() -> None:
with (
patch(
"core.plugin.backwards_invocation.model.ModelInvocationUtils.get_max_llm_context_tokens",
Expand Down Expand Up @@ -122,7 +123,7 @@ def test_system_model_helpers_forward_user_id():
)


def test_invoke_summary_uses_same_user_scope_for_token_helpers():
def test_invoke_summary_uses_same_user_scope_for_token_helpers() -> None:
tenant = Tenant(name="Test Workspace")
tenant.id = "tenant-1"
payload = RequestInvokeSummary(text="short", instruction="keep it concise")
Expand Down Expand Up @@ -239,7 +240,7 @@ def get_model_schema(self) -> _ModelSchema:
return self.schema


def test_invoke_text_embedding_forwards_input_type_to_bound_model_instance():
def test_invoke_text_embedding_forwards_input_type_to_bound_model_instance() -> None:
tenant = _backwards_invocation_model_tenant()
payload = RequestInvokeTextEmbedding(
provider="langgenius/openai/openai",
Expand Down Expand Up @@ -270,7 +271,7 @@ def test_invoke_text_embedding_forwards_input_type_to_bound_model_instance():
]


def test_invoke_multimodal_embedding_uses_bound_model_instance():
def test_invoke_multimodal_embedding_uses_bound_model_instance() -> None:
tenant = _backwards_invocation_model_tenant()
documents = [{"content": "AQID", "content_type": "image", "file_id": "asset.png"}]
payload = RequestInvokeMultimodalEmbedding(
Expand Down Expand Up @@ -305,7 +306,7 @@ def test_invoke_multimodal_embedding_uses_bound_model_instance():
]


def test_invoke_rerank_accepts_omitted_threshold_and_top_n():
def test_invoke_rerank_accepts_omitted_threshold_and_top_n() -> None:
tenant = _backwards_invocation_model_tenant()
payload = RequestInvokeRerank(
provider="langgenius/cohere/cohere",
Expand Down Expand Up @@ -335,7 +336,9 @@ def test_invoke_rerank_accepts_omitted_threshold_and_top_n():
("model_type", "selected"),
[(ModelType.TEXT_EMBEDDING, False), (ModelType.LLM, False), (ModelType.LLM, True)],
)
def test_list_models_returns_only_active_tenant_models_with_installed_identity(model_type, selected):
def test_list_models_returns_only_active_tenant_models_with_installed_identity(
model_type: Literal[ModelType.LLM, ModelType.TEXT_EMBEDDING], selected: bool
) -> None:
model_name = "selected-model"
payload = RequestListModels(model_type=model_type, model=model_name if selected else None, limit=10)
provider = _ModelProvider(provider="langgenius/openai/openai")
Expand Down Expand Up @@ -395,7 +398,7 @@ def test_list_models_returns_only_active_tenant_models_with_installed_identity(m


@pytest.mark.parametrize("parameter", ["max_tokens", "max_completion_tokens", "maxtoken", "custom_limit"])
def test_model_token_limits_use_schema_maximum_not_default(parameter):
def test_model_token_limits_use_schema_maximum_not_default(parameter: str) -> None:
schema = _ModelSchema(
model_properties={"context_size": 131072},
parameter_rules=[_ParameterRule(name=parameter, use_template="max_tokens", max=32768.0, default=1024)],
Expand All @@ -405,6 +408,7 @@ def test_model_token_limits_use_schema_maximum_not_default(parameter):
limits = PluginModelBackwardsInvocation._model_token_limits(
tenant_id="tenant-1", user_id="user-1", provider="plugin/provider", model="selected-model"
)
assert limits is not None
assert limits.context_tokens == 131072
assert limits.max_output_tokens == 32768
assert limits.output_parameter == parameter
Expand All @@ -418,7 +422,7 @@ def test_model_token_limits_use_schema_maximum_not_default(parameter):


@pytest.mark.parametrize("maximum", [None, 0, -1, True, float("inf"), float("nan"), 10**400, 12.5, "32768"])
def test_model_token_limits_do_not_invent_unknown_output_capacity(maximum):
def test_model_token_limits_do_not_invent_unknown_output_capacity(maximum: object) -> None:
schema = _ModelSchema(
model_properties={"context_size": 8192},
parameter_rules=[_ParameterRule(name="max_tokens", use_template=None, max=maximum, default=4096)],
Expand All @@ -431,11 +435,12 @@ def test_model_token_limits_do_not_invent_unknown_output_capacity(maximum):
limits = PluginModelBackwardsInvocation._model_token_limits(
tenant_id="tenant-1", user_id="user-1", provider="plugin/provider", model="selected-model"
)
assert limits is not None
assert limits.context_tokens == 8192
assert limits.max_output_tokens is None


def test_model_token_limits_respect_the_tighter_declared_alias():
def test_model_token_limits_respect_the_tighter_declared_alias() -> None:
schema = _ModelSchema(
model_properties={},
parameter_rules=[
Expand All @@ -451,11 +456,12 @@ def test_model_token_limits_respect_the_tighter_declared_alias():
limits = PluginModelBackwardsInvocation._model_token_limits(
tenant_id="tenant-1", user_id="user-1", provider="plugin/provider", model="selected-model"
)
assert limits is not None
assert limits.max_output_tokens == 8192
assert limits.output_parameter == "max_output_tokens"


def test_invoke_tts_emits_the_verified_mime_type_for_backwards_invocation():
def test_invoke_tts_emits_the_verified_mime_type_for_backwards_invocation() -> None:
tenant = Tenant(name="Test Workspace")
tenant.id = "tenant-1"
model_instance = _tts_model_instance()
Expand Down Expand Up @@ -485,7 +491,7 @@ def test_invoke_tts_emits_the_verified_mime_type_for_backwards_invocation():
]


def test_invoke_tts_defers_provider_errors_until_the_response_generator_is_consumed():
def test_invoke_tts_defers_provider_errors_until_the_response_generator_is_consumed() -> None:
tenant = Tenant(name="Test Workspace")
tenant.id = "tenant-1"
model_instance = _tts_model_instance()
Expand All @@ -509,7 +515,7 @@ def test_invoke_tts_defers_provider_errors_until_the_response_generator_is_consu
next(response)


def test_backwards_event_stream_serializes_a_deferred_mime_error():
def test_backwards_event_stream_serializes_a_deferred_mime_error() -> None:
tenant = Tenant(name="Test Workspace")
tenant.id = "tenant-1"
model_instance = _tts_model_instance()
Expand Down
43 changes: 22 additions & 21 deletions api/tests/unit_tests/core/plugin/utils/test_chunk_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@


class TestChunkMerger:
def test_file_chunk_initialization(self):
def test_file_chunk_initialization(self) -> None:
"""Test FileChunk initialization."""
chunk = FileChunk(1024)
assert chunk.bytes_written == 0
assert chunk.total_length == 1024
assert len(chunk.data) == 1024

def test_merge_blob_chunks_with_single_complete_chunk(self):
def test_merge_blob_chunks_with_single_complete_chunk(self) -> None:
"""Test merging a single complete blob chunk."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -43,7 +43,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
# The buffer should contain the complete data
assert result[0].message.blob[:10] == b"HelloWorld"

def test_merge_blob_chunks_with_multiple_files(self):
def test_merge_blob_chunks_with_multiple_files(self) -> None:
"""Test merging chunks from multiple files."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -81,7 +81,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
# Check that both files are properly merged
assert all(r.type == ToolInvokeMessage.MessageType.BLOB for r in result)

def test_merge_blob_chunks_passes_through_non_blob_messages(self):
def test_merge_blob_chunks_passes_through_non_blob_messages(self) -> None:
"""Test that non-blob messages pass through unchanged."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -113,7 +113,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
assert isinstance(result[2].message, ToolInvokeMessage.TextMessage)
assert result[2].message.text == "World"

def test_merge_blob_chunks_file_too_large(self):
def test_merge_blob_chunks_file_too_large(self) -> None:
"""Test that error is raised when file exceeds max size."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -131,7 +131,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
list(merge_blob_chunks(mock_generator(), max_file_size=1000))
assert "File is too large" in str(exc_info.value)

def test_merge_blob_chunks_chunk_too_large(self):
def test_merge_blob_chunks_chunk_too_large(self) -> None:
"""Test that error is raised when chunk exceeds max chunk size."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -147,7 +147,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
list(merge_blob_chunks(mock_generator(), max_chunk_size=8192))
assert "File chunk is too large" in str(exc_info.value)

def test_merge_blob_chunks_with_agent_invoke_message(self):
def test_merge_blob_chunks_with_agent_invoke_message(self) -> None:
"""Test that merge_blob_chunks works with AgentInvokeMessage."""

def mock_generator() -> Generator[AgentInvokeMessage]:
Expand All @@ -171,7 +171,7 @@ def mock_generator() -> Generator[AgentInvokeMessage]:
assert isinstance(result[0], AgentInvokeMessage)
assert result[0].type == AgentInvokeMessage.MessageType.BLOB

def test_merge_blob_chunks_preserves_meta(self):
def test_merge_blob_chunks_preserves_meta(self) -> None:
"""Test that meta information is preserved in merged messages."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -187,7 +187,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
assert len(result) == 1
assert result[0].meta == {"key": "value"}

def test_merge_blob_chunks_custom_limits(self):
def test_merge_blob_chunks_custom_limits(self) -> None:
"""Test merge_blob_chunks with custom size limits."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -221,7 +221,7 @@ def mock_generator2() -> Generator[ToolInvokeMessage]:
with pytest.raises(ValueError, match="File is too large which reached the limit of 0\\.000286102294921875MB"):
list(merge_blob_chunks(mock_generator2(), max_file_size=300))

def test_merge_blob_chunks_data_integrity(self):
def test_merge_blob_chunks_data_integrity(self) -> None:
"""Test that merged chunks exactly match the original data."""
# Create original data
original_data = b"This is a test message that will be split into chunks for testing purposes."
Expand Down Expand Up @@ -255,7 +255,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
# Verify the merged data exactly matches the original
assert result[0].message.blob == original_data

def test_merge_blob_chunks_empty_chunk(self):
def test_merge_blob_chunks_empty_chunk(self) -> None:
"""Test handling of empty chunks."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -288,7 +288,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
# The final blob should contain "Hello" followed by "World"
assert result[0].message.blob[:10] == b"HelloWorld"

def test_merge_blob_chunks_single_chunk_file(self):
def test_merge_blob_chunks_single_chunk_file(self) -> None:
"""Test file that arrives as a single complete chunk."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -310,7 +310,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
assert isinstance(result[0].message, ToolInvokeMessage.BlobMessage)
assert result[0].message.blob == b"Single Data"

def test_merge_blob_chunks_concurrent_files(self):
def test_merge_blob_chunks_concurrent_files(self) -> None:
"""Test that chunks from different files are properly separated."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -358,7 +358,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
expected = {b"First file content", b"Second file data", b"Third file"}
assert blobs == expected

def test_merge_blob_chunks_exact_buffer_size(self):
def test_merge_blob_chunks_exact_buffer_size(self) -> None:
"""Test that data fitting exactly in buffer works correctly."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -392,7 +392,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
assert len(result[0].message.blob) == 100
assert result[0].message.blob == b"X" * 100

def test_merge_blob_chunks_large_file_simulation(self):
def test_merge_blob_chunks_large_file_simulation(self) -> None:
"""Test handling of a large file split into many chunks."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down Expand Up @@ -433,7 +433,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
chunk = merged_data[chunk_start:chunk_end]
assert all(b == expected_byte for b in chunk), f"Chunk {i} has incorrect data"

def test_merge_blob_chunks_sequential_order_required(self):
def test_merge_blob_chunks_sequential_order_required(self) -> None:
"""
Test note: The current implementation assumes chunks arrive in sequential order.
Out-of-order chunks would need additional logic to handle properly.
Expand Down Expand Up @@ -465,7 +465,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:


class TestConverter:
def test_convert_parameters_to_plugin_format_with_single_file_and_selector(self):
def test_convert_parameters_to_plugin_format_with_single_file_and_selector(self) -> None:
file_param = File(
tenant_id="tenant-1",
file_type=FileType.IMAGE,
Expand Down Expand Up @@ -498,7 +498,7 @@ def test_convert_parameters_to_plugin_format_with_single_file_and_selector(self)
assert converted["selector"]["provider_id"] == "org/plugin/provider"
assert converted["plain"] == 123

def test_convert_parameters_to_plugin_format_with_lists_and_passthrough_values(self):
def test_convert_parameters_to_plugin_format_with_lists_and_passthrough_values(self) -> None:
file_one = File(
tenant_id="tenant-1",
file_type=FileType.DOCUMENT,
Expand Down Expand Up @@ -530,10 +530,11 @@ def test_convert_parameters_to_plugin_format_with_lists_and_passthrough_values(s
tool_parameters={},
)

empty_list: list[object] = []
params = {
"files": [file_one, file_two],
"selectors": [selector_one, selector_two],
"empty_list": [],
"empty_list": empty_list,
"mixed_list": [file_one, "raw"],
"none_value": None,
}
Expand All @@ -549,7 +550,7 @@ def test_convert_parameters_to_plugin_format_with_lists_and_passthrough_values(s
assert converted["mixed_list"] == [file_one, "raw"]
assert converted["none_value"] is None

def test_merge_blob_chunks_rejects_declared_size_above_limit(self):
def test_merge_blob_chunks_rejects_declared_size_above_limit(self) -> None:
"""A declared total_length above the limit is rejected before allocation."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand All @@ -563,7 +564,7 @@ def mock_generator() -> Generator[ToolInvokeMessage]:
with pytest.raises(ValueError, match="File is too large"):
list(merge_blob_chunks(mock_generator(), max_file_size=1024))

def test_merge_blob_chunks_rejects_absurd_declared_size(self):
def test_merge_blob_chunks_rejects_absurd_declared_size(self) -> None:
"""An absurd declared total_length raises ValueError, not MemoryError/OverflowError."""

def mock_generator() -> Generator[ToolInvokeMessage]:
Expand Down
2 changes: 0 additions & 2 deletions api/tests/unit_tests/pyrefly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,9 @@ project-excludes = [
"core/plugin/impl/test_oauth.py",
"core/plugin/impl/test_trigger.py",
"core/plugin/backwards_invocation/test_app.py",
"core/plugin/backwards_invocation/test_model.py",
"core/plugin/impl/test_model_runtime.py",
"core/plugin/entities/test_plugin_daemon.py",
"core/plugin/impl/test_plugin.py",
"core/plugin/utils/test_chunk_merger.py",
"core/prompt/test_advanced_prompt_transform.py",
"core/prompt/utils/test_extract_thread_messages.py",
"core/rag/cleaner/test_clean_processor.py",
Expand Down