diff --git a/ollama/_types.py b/ollama/_types.py index 96529d63..8f3e8f7e 100644 --- a/ollama/_types.py +++ b/ollama/_types.py @@ -572,7 +572,7 @@ class ShowResponse(SubscriptableBaseModel): details: Optional[ModelDetails] = None - modelinfo: Optional[Mapping[str, Any]] = Field(alias='model_info') + modelinfo: Optional[Mapping[str, Any]] = Field(None, alias='model_info') parameters: Optional[str] = None diff --git a/tests/test_type_serialization.py b/tests/test_type_serialization.py index f458cd23..c22630ba 100644 --- a/tests/test_type_serialization.py +++ b/tests/test_type_serialization.py @@ -4,7 +4,7 @@ import pytest -from ollama._types import CreateRequest, Image +from ollama._types import CreateRequest, Image, ShowResponse def test_image_serialization_bytes(): @@ -92,3 +92,48 @@ def test_create_request_serialization_license_list(): request = CreateRequest(model='test-model', license=['MIT', 'Apache-2.0']) serialized = request.model_dump() assert serialized['license'] == ['MIT', 'Apache-2.0'] + + +def test_show_response_missing_model_info(): + payload = { + 'modelfile': '# Modelfile generated by "ollama show"...', + 'template': '{{ .Prompt }}', + 'details': { + 'parent_model': '', + 'format': '', + 'family': '', + 'families': None, + 'parameter_size': '', + 'quantization_level': '' + }, + 'capabilities': ['completion', 'tools', 'thinking'], + 'modified_at': '2025-12-24T10:08:53.438277171-05:00', + } + + response = ShowResponse.model_validate(payload) + assert response.modelinfo is None + assert response.capabilities == ['completion', 'tools', 'thinking'] + assert response.template == '{{ .Prompt }}' + + +def test_show_response_with_model_info(): + payload = { + 'modelfile': '# Modelfile generated by "ollama show"...', + 'template': '{{ .Prompt }}', + 'details': { + 'parent_model': '', + 'format': '', + 'family': '', + 'families': None, + 'parameter_size': '', + 'quantization_level': '' + }, + 'capabilities': ['completion', 'tools', 'thinking'], + 'modified_at': '2025-12-24T10:08:53.438277171-05:00', + 'model_info': {'general.architecture': 'llama'}, + } + + response = ShowResponse.model_validate(payload) + assert response.modelinfo == {'general.architecture': 'llama'} + assert response.capabilities == ['completion', 'tools', 'thinking'] + assert response.template == '{{ .Prompt }}' diff --git a/verify_fix.py b/verify_fix.py new file mode 100644 index 00000000..d7d3fe01 --- /dev/null +++ b/verify_fix.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +Verification script for the ShowResponse model_info issue #607. +Tests both the missing model_info scenario (cloud models) and normal scenario with model_info. +""" + +import sys +from pydantic import ValidationError +from ollama._types import ShowResponse + +def test_missing_model_info(): + """Test payload without model_info key (cloud models scenario).""" + payload_without_model_info = { + 'modelfile': '# Modelfile generated by "ollama show"...', + 'template': '{{ .Prompt }}', + 'details': { + 'parent_model': '', + 'format': '', + 'family': '', + 'families': None, + 'parameter_size': '', + 'quantization_level': '' + }, + 'capabilities': ['completion', 'tools', 'thinking'], + 'modified_at': '2025-12-24T10:08:53.438277171-05:00', + } + + try: + result = ShowResponse.model_validate(payload_without_model_info) + print("✓ PASS: Missing model_info payload validated successfully") + print(f" result.modelinfo = {result.modelinfo}") + return True + except ValidationError as e: + print("✗ FAIL: Missing model_info payload caused ValidationError") + print(f" Error: {e}") + return False + +def test_with_model_info(): + """Test payload with model_info key (normal scenario).""" + payload_with_model_info = { + 'modelfile': '# Modelfile generated by "ollama show"...', + 'template': '{{ .Prompt }}', + 'details': { + 'parent_model': '', + 'format': '', + 'family': '', + 'families': None, + 'parameter_size': '', + 'quantization_level': '' + }, + 'capabilities': ['completion', 'tools', 'thinking'], + 'modified_at': '2025-12-24T10:08:53.438277171-05:00', + 'model_info': {'general.architecture': 'llama'}, + } + + try: + result = ShowResponse.model_validate(payload_with_model_info) + expected_model_info = {'general.architecture': 'llama'} + if result.modelinfo == expected_model_info: + print("✓ PASS: With model_info payload validated and populated correctly") + print(f" result.modelinfo = {result.modelinfo}") + return True + else: + print("✗ FAIL: With model_info payload validated but modelinfo incorrect") + print(f" Expected: {expected_model_info}") + print(f" Actual: {result.modelinfo}") + return False + except ValidationError as e: + print("✗ FAIL: With model_info payload caused ValidationError") + print(f" Error: {e}") + return False + +def main(): + print("=== Testing ShowResponse model_info handling ===") + print() + + # Test missing model_info (the bug scenario) + missing_passed = test_missing_model_info() + print() + + # Test with model_info (positive control) + with_passed = test_with_model_info() + print() + + # Determine if current state is as expected + if missing_passed and with_passed: + print("=== CURRENT STATE: FIXED ===") + print("Both tests passed - model_info is properly optional") + return 0 + elif not missing_passed and with_passed: + print("=== CURRENT STATE: BUGGY ===") + print("Missing model_info fails (expected for unfixed code)") + return 0 + else: + print("=== CURRENT STATE: UNEXPECTED ===") + print("Test results don't match expected patterns") + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file