mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-23 21:06:50 +00:00
438 lines
15 KiB
Python
438 lines
15 KiB
Python
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from camel.models import ModelProcessingError
|
|
|
|
from app.component.model_validation import (
|
|
EXPECTED_TOOL_RESULT,
|
|
ValidationErrorType,
|
|
ValidationResult,
|
|
ValidationStage,
|
|
categorize_error,
|
|
create_agent,
|
|
format_raw_error,
|
|
validate_model_with_details,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_validation_result_initialization():
|
|
"""Test ValidationResult initialization."""
|
|
result = ValidationResult()
|
|
assert result.is_valid is False
|
|
assert result.is_tool_calls is False
|
|
assert result.error_type is None
|
|
assert result.error_code is None
|
|
assert result.error_message is None
|
|
assert result.raw_error_message is None
|
|
assert result.error_details == {}
|
|
assert result.validation_stages == {}
|
|
assert result.diagnostic_info == {}
|
|
assert result.successful_stages == []
|
|
assert result.failed_stage is None
|
|
assert result.model_response_info is None
|
|
assert result.tool_call_info is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_validation_result_to_dict():
|
|
"""Test ValidationResult.to_dict() method."""
|
|
result = ValidationResult()
|
|
result.is_valid = True
|
|
result.is_tool_calls = True
|
|
result.error_type = ValidationErrorType.AUTHENTICATION_ERROR
|
|
result.error_message = "Test error"
|
|
result.raw_error_message = "Raw test error"
|
|
result.validation_stages[ValidationStage.INITIALIZATION] = True
|
|
result.successful_stages.append(ValidationStage.INITIALIZATION)
|
|
result.failed_stage = ValidationStage.MODEL_CREATION
|
|
|
|
result_dict = result.to_dict()
|
|
assert result_dict["is_valid"] is True
|
|
assert result_dict["is_tool_calls"] is True
|
|
assert result_dict["error_type"] == "authentication_error"
|
|
assert result_dict["error_message"] == "Test error"
|
|
assert result_dict["raw_error_message"] == "Raw test error"
|
|
assert result_dict["validation_stages"]["initialization"] is True
|
|
assert "initialization" in result_dict["successful_stages"]
|
|
assert result_dict["failed_stage"] == "model_creation"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_format_short_error():
|
|
"""Test formatting of short error message."""
|
|
error = ValueError("Short error message")
|
|
result = format_raw_error(error)
|
|
assert result == "Short error message"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_format_long_error():
|
|
"""Test formatting of long error message with truncation."""
|
|
long_message = "A" * 500
|
|
error = ValueError(long_message)
|
|
result = format_raw_error(error, max_length=100)
|
|
assert len(result) == 103 # 100 + "..."
|
|
assert result.endswith("...")
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_format_error_with_default_max_length():
|
|
"""Test formatting with default max_length."""
|
|
long_message = "A" * 400
|
|
error = ValueError(long_message)
|
|
result = format_raw_error(error)
|
|
assert len(result) == 303 # 300 + "..."
|
|
assert result.endswith("...")
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_timeout_error():
|
|
"""Test categorization of timeout errors."""
|
|
error = TimeoutError("Request timed out")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.TIMEOUT_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_connection_error():
|
|
"""Test categorization of connection errors."""
|
|
error = ConnectionError("Connection failed")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.NETWORK_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_model_processing_error_401():
|
|
"""Test categorization of 401 errors from ModelProcessingError."""
|
|
error = ModelProcessingError("401 Unauthorized")
|
|
result = categorize_error(error, ValidationStage.MODEL_CREATION)
|
|
assert result == ValidationErrorType.AUTHENTICATION_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_model_processing_error_404():
|
|
"""Test categorization of 404 errors from ModelProcessingError."""
|
|
error = ModelProcessingError("404 Model not found")
|
|
result = categorize_error(error, ValidationStage.MODEL_CREATION)
|
|
assert result == ValidationErrorType.MODEL_NOT_FOUND
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_model_processing_error_429():
|
|
"""Test categorization of 429 errors from ModelProcessingError."""
|
|
error = ModelProcessingError("429 Rate limit exceeded")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.RATE_LIMIT_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_model_processing_error_quota():
|
|
"""Test categorization of quota errors from ModelProcessingError."""
|
|
error = ModelProcessingError("Insufficient quota")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.QUOTA_EXCEEDED
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_unknown_error():
|
|
"""Test categorization of unknown errors."""
|
|
error = ValueError("Some random error")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.UNKNOWN_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_http_status_code_401():
|
|
"""Test categorization based on HTTP status code 401."""
|
|
error = Exception("401 Unauthorized access")
|
|
result = categorize_error(error, ValidationStage.MODEL_CREATION)
|
|
assert result == ValidationErrorType.AUTHENTICATION_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_http_status_code_404():
|
|
"""Test categorization based on HTTP status code 404."""
|
|
error = Exception("404 Not found")
|
|
result = categorize_error(error, ValidationStage.MODEL_CREATION)
|
|
assert result == ValidationErrorType.MODEL_NOT_FOUND
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_http_status_code_429():
|
|
"""Test categorization based on HTTP status code 429."""
|
|
error = Exception("429 Too many requests")
|
|
result = categorize_error(error, ValidationStage.MODEL_CALL)
|
|
assert result == ValidationErrorType.RATE_LIMIT_ERROR
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_create_agent_success(mock_chat_agent, mock_model_factory):
|
|
"""Test successful agent creation."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent_instance = MagicMock()
|
|
mock_chat_agent.return_value = mock_agent_instance
|
|
|
|
agent = create_agent(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
mock_model_factory.assert_called_once()
|
|
mock_chat_agent.assert_called_once()
|
|
assert agent == mock_agent_instance
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_create_agent_invalid_model_type():
|
|
"""Test agent creation with invalid model type."""
|
|
with pytest.raises(ValueError, match="Invalid model_type"):
|
|
create_agent(model_platform="OPENAI", model_type=None)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_create_agent_invalid_model_platform():
|
|
"""Test agent creation with invalid model platform."""
|
|
with pytest.raises(ValueError, match="Invalid model_platform"):
|
|
create_agent(model_platform=None, model_type="GPT_4O_MINI")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_create_agent_hardcodes_bedrock_converse_region(
|
|
mock_chat_agent, mock_model_factory
|
|
):
|
|
"""Test Bedrock Converse validation always uses the hardcoded region."""
|
|
mock_model_factory.return_value = MagicMock()
|
|
mock_chat_agent.return_value = MagicMock()
|
|
|
|
create_agent(
|
|
model_platform="aws-bedrock-converse",
|
|
model_type="anthropic.claude-3-5-sonnet",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert mock_model_factory.call_args.kwargs["region_name"] == "us-west-2"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_validation_missing_model_type():
|
|
"""Test validation with missing model type."""
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI", model_type=""
|
|
)
|
|
assert result.is_valid is False
|
|
assert result.error_type == ValidationErrorType.INVALID_CONFIGURATION
|
|
assert result.failed_stage == ValidationStage.INITIALIZATION
|
|
assert "Model type is required" in result.error_message
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_validation_missing_model_platform():
|
|
"""Test validation with missing model platform."""
|
|
result = validate_model_with_details(
|
|
model_platform="", model_type="GPT_4O_MINI"
|
|
)
|
|
assert result.is_valid is False
|
|
assert result.error_type == ValidationErrorType.INVALID_CONFIGURATION
|
|
assert result.failed_stage == ValidationStage.INITIALIZATION
|
|
assert "Model platform is required" in result.error_message
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
def test_validation_model_creation_failure(mock_model_factory):
|
|
"""Test validation when model creation fails."""
|
|
mock_model_factory.side_effect = ModelProcessingError("401 Unauthorized")
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="invalid_key",
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert result.error_type == ValidationErrorType.AUTHENTICATION_ERROR
|
|
assert result.failed_stage == ValidationStage.MODEL_CREATION
|
|
assert result.validation_stages[ValidationStage.INITIALIZATION] is True
|
|
assert ValidationStage.INITIALIZATION in result.successful_stages
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_agent_creation_failure(
|
|
mock_chat_agent, mock_model_factory
|
|
):
|
|
"""Test validation when agent creation fails."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_chat_agent.side_effect = Exception("Agent creation failed")
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert result.failed_stage == ValidationStage.AGENT_CREATION
|
|
assert result.validation_stages[ValidationStage.MODEL_CREATION] is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_model_call_failure(mock_chat_agent, mock_model_factory):
|
|
"""Test validation when model call fails."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent = MagicMock()
|
|
mock_agent.step.side_effect = Exception("Model call failed")
|
|
mock_chat_agent.return_value = mock_agent
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert result.failed_stage == ValidationStage.MODEL_CALL
|
|
assert result.validation_stages[ValidationStage.AGENT_CREATION] is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_no_tool_calls(mock_chat_agent, mock_model_factory):
|
|
"""Test validation when model doesn't make tool calls."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.info = {"tool_calls": []} # No tool calls
|
|
mock_agent.step.return_value = mock_response
|
|
mock_chat_agent.return_value = mock_agent
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert result.is_tool_calls is False
|
|
assert result.error_type == ValidationErrorType.TOOL_CALL_NOT_SUPPORTED
|
|
assert result.failed_stage == ValidationStage.TOOL_CALL_EXECUTION
|
|
assert result.validation_stages[ValidationStage.MODEL_CALL] is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_tool_call_execution_failed(
|
|
mock_chat_agent, mock_model_factory
|
|
):
|
|
"""Test validation when tool call execution fails."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent = MagicMock()
|
|
mock_response = MagicMock()
|
|
tool_call = MagicMock()
|
|
tool_call.result = "Wrong result" # Wrong result
|
|
mock_response.info = {"tool_calls": [tool_call]}
|
|
mock_agent.step.return_value = mock_response
|
|
mock_chat_agent.return_value = mock_agent
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert result.is_tool_calls is False
|
|
assert result.error_type == ValidationErrorType.TOOL_CALL_EXECUTION_FAILED
|
|
assert result.failed_stage == ValidationStage.TOOL_CALL_EXECUTION
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_success_with_tool_calls(
|
|
mock_chat_agent, mock_model_factory
|
|
):
|
|
"""Test successful validation with tool calls."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent = MagicMock()
|
|
mock_response = MagicMock()
|
|
tool_call = MagicMock()
|
|
tool_call.result = EXPECTED_TOOL_RESULT # Correct result
|
|
mock_response.info = {"tool_calls": [tool_call]}
|
|
mock_agent.step.return_value = mock_response
|
|
mock_chat_agent.return_value = mock_agent
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert result.is_valid is True
|
|
assert result.is_tool_calls is True
|
|
assert result.error_type is None
|
|
assert result.failed_stage is None
|
|
assert (
|
|
result.validation_stages[ValidationStage.TOOL_CALL_EXECUTION] is True
|
|
)
|
|
assert ValidationStage.TOOL_CALL_EXECUTION in result.successful_stages
|
|
|
|
|
|
@pytest.mark.unit
|
|
@patch("app.component.model_validation.ModelFactory.create")
|
|
@patch("app.component.model_validation.ChatAgent")
|
|
def test_validation_diagnostic_info(mock_chat_agent, mock_model_factory):
|
|
"""Test that diagnostic info is properly populated."""
|
|
mock_model = MagicMock()
|
|
mock_model_factory.return_value = mock_model
|
|
mock_agent = MagicMock()
|
|
mock_response = MagicMock()
|
|
tool_call = MagicMock()
|
|
tool_call.result = EXPECTED_TOOL_RESULT
|
|
mock_response.info = {"tool_calls": [tool_call]}
|
|
mock_agent.step.return_value = mock_response
|
|
mock_chat_agent.return_value = mock_agent
|
|
|
|
result = validate_model_with_details(
|
|
model_platform="OPENAI",
|
|
model_type="GPT_4O_MINI",
|
|
api_key="test_key",
|
|
)
|
|
|
|
assert "initialization" in result.diagnostic_info
|
|
assert "model_creation" in result.diagnostic_info
|
|
assert "agent_creation" in result.diagnostic_info
|
|
assert result.model_response_info is not None
|
|
assert result.tool_call_info is not None
|
|
assert result.tool_call_info["execution_successful"] is True
|