97e91a83f3
Ruff / Ruff (push) Waiting to run
Test / Core Tests (push) Waiting to run
Test / Offline Coverage Tests (Python 3.10) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.11) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.12) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.13) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.9) (push) Waiting to run
Test / Full Coverage (Python 3.11) (push) Waiting to run
Test / Core Provider Tests (OpenAI) (push) Blocked by required conditions
Test / Core Provider Tests (Anthropic) (push) Blocked by required conditions
Test / Core Provider Tests (Google) (push) Blocked by required conditions
Test / Core Provider Tests (Other) (push) Blocked by required conditions
Test / Anthropic Tests (push) Blocked by required conditions
Test / Gemini Tests (push) Blocked by required conditions
Test / Google GenAI Tests (push) Blocked by required conditions
Test / Vertex AI Tests (push) Blocked by required conditions
Test / OpenAI Tests (push) Blocked by required conditions
Test / Writer Tests (push) Blocked by required conditions
Test / Auto Client Tests (push) Blocked by required conditions
ty / type-check (push) Waiting to run
201 lines
6.5 KiB
Python
201 lines
6.5 KiB
Python
"""Tests for the new schema_utils functions."""
|
|
|
|
import pytest
|
|
from pydantic import BaseModel, Field
|
|
from typing import Optional
|
|
|
|
from instructor.processing.schema import (
|
|
generate_openai_schema,
|
|
generate_anthropic_schema,
|
|
generate_gemini_schema,
|
|
)
|
|
from instructor.processing.function_calls import ResponseSchema, OpenAISchema
|
|
from instructor.v2.providers.anthropic.schema import (
|
|
generate_anthropic_schema as generate_provider_anthropic_schema,
|
|
)
|
|
from instructor.v2.providers.openai.schema import (
|
|
generate_openai_schema as generate_provider_openai_schema,
|
|
)
|
|
|
|
|
|
class TestModel(BaseModel):
|
|
"""A test model for schema generation."""
|
|
|
|
__test__ = False
|
|
|
|
name: str = Field(description="The name of the user")
|
|
age: int = Field(description="The age of the user")
|
|
email: Optional[str] = Field(default=None, description="The email address")
|
|
|
|
|
|
class TestModelWithDocstring(BaseModel):
|
|
"""A model with parameter docstring.
|
|
|
|
Args:
|
|
name: The full name
|
|
age: Age in years
|
|
tags: List of tags
|
|
"""
|
|
|
|
__test__ = False
|
|
|
|
name: str
|
|
age: int
|
|
tags: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class TestModelOldStyle(TestModel, ResponseSchema):
|
|
"""Test model inheriting from ResponseSchema."""
|
|
|
|
pass
|
|
|
|
|
|
class TestModelOldStyleAlias(TestModel, OpenAISchema):
|
|
"""Test model inheriting from OpenAISchema alias for backward compatibility."""
|
|
|
|
pass
|
|
|
|
|
|
def test_generate_openai_schema_matches_class_method():
|
|
"""Test that generate_openai_schema produces identical output to the class method."""
|
|
# Compare with old style inheritance - but use the same model for both
|
|
standalone_schema = generate_openai_schema(TestModelOldStyle)
|
|
class_schema = TestModelOldStyle.openai_schema
|
|
|
|
assert standalone_schema == class_schema
|
|
|
|
# Test structure
|
|
assert "name" in standalone_schema
|
|
assert "description" in standalone_schema
|
|
assert "parameters" in standalone_schema
|
|
assert "properties" in standalone_schema["parameters"]
|
|
assert "required" in standalone_schema["parameters"]
|
|
|
|
|
|
def test_generate_openai_schema_compatibility_wrapper_delegates_to_provider():
|
|
"""Shared helper keeps the old API while reusing provider-local logic."""
|
|
assert generate_openai_schema(TestModelOldStyle) is generate_provider_openai_schema(
|
|
TestModelOldStyle
|
|
)
|
|
|
|
|
|
def test_generate_anthropic_schema_matches_class_method():
|
|
"""Test that generate_anthropic_schema produces identical output to the class method."""
|
|
standalone_schema = generate_anthropic_schema(TestModelOldStyle)
|
|
class_schema = TestModelOldStyle.anthropic_schema
|
|
|
|
assert standalone_schema == class_schema
|
|
|
|
# Test structure
|
|
assert "name" in standalone_schema
|
|
assert "description" in standalone_schema
|
|
assert "input_schema" in standalone_schema
|
|
|
|
|
|
def test_generate_anthropic_schema_compatibility_wrapper_delegates_to_provider():
|
|
"""Shared helper keeps the old API while reusing provider-local logic."""
|
|
assert generate_anthropic_schema(
|
|
TestModelOldStyle
|
|
) is generate_provider_anthropic_schema(TestModelOldStyle)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
True, reason="google.generativeai not installed in test environment"
|
|
)
|
|
def test_generate_gemini_schema_matches_class_method():
|
|
"""Test that generate_gemini_schema produces identical output to the class method."""
|
|
# This will trigger deprecation warnings, which is expected
|
|
with pytest.warns(DeprecationWarning):
|
|
standalone_schema = generate_gemini_schema(TestModelOldStyle)
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
class_schema = TestModelOldStyle.gemini_schema
|
|
|
|
# Both should be FunctionDeclaration objects with same attributes
|
|
assert type(standalone_schema) == type(class_schema)
|
|
assert standalone_schema.name == class_schema.name
|
|
assert standalone_schema.description == class_schema.description
|
|
|
|
|
|
def test_docstring_parameter_enrichment():
|
|
"""Test that docstring parameters are properly extracted."""
|
|
schema = generate_openai_schema(TestModelWithDocstring)
|
|
|
|
# The description should come from the docstring
|
|
assert "parameter docstring" in schema["description"].lower()
|
|
|
|
# Parameters should be extracted from docstring Args section
|
|
# This is handled by docstring_parser, so we test the integration
|
|
assert "parameters" in schema
|
|
assert "properties" in schema["parameters"]
|
|
|
|
|
|
def test_schema_caching():
|
|
"""Test that LRU cache works correctly."""
|
|
# Call twice and verify it's cached (same object reference)
|
|
schema1 = generate_openai_schema(TestModel)
|
|
schema2 = generate_openai_schema(TestModel)
|
|
|
|
# Should be the same cached result
|
|
assert schema1 is schema2
|
|
|
|
|
|
def test_required_fields_generation():
|
|
"""Test that required fields are correctly identified."""
|
|
schema = generate_openai_schema(TestModel)
|
|
|
|
# name and age are required, email is optional
|
|
required = schema["parameters"]["required"]
|
|
assert "name" in required
|
|
assert "age" in required
|
|
assert "email" not in required
|
|
|
|
|
|
def test_field_descriptions():
|
|
"""Test that field descriptions are preserved."""
|
|
schema = generate_openai_schema(TestModel)
|
|
properties = schema["parameters"]["properties"]
|
|
|
|
assert properties["name"]["description"] == "The name of the user"
|
|
assert properties["age"]["description"] == "The age of the user"
|
|
assert properties["email"]["description"] == "The email address"
|
|
|
|
|
|
def test_schema_name_and_title():
|
|
"""Test that schema name comes from model title."""
|
|
schema = generate_openai_schema(TestModel)
|
|
|
|
assert schema["name"] == "TestModel"
|
|
|
|
|
|
def test_no_inheritance_required():
|
|
"""Test that models don't need to inherit from ResponseSchema."""
|
|
|
|
# Plain Pydantic model should work
|
|
class PlainModel(BaseModel):
|
|
value: str
|
|
|
|
schema = generate_openai_schema(PlainModel)
|
|
|
|
assert schema["name"] == "PlainModel"
|
|
assert "parameters" in schema
|
|
assert "value" in schema["parameters"]["properties"]
|
|
|
|
|
|
def test_anthropic_schema_uses_openai_base():
|
|
"""Test that Anthropic schema reuses OpenAI schema data."""
|
|
openai_schema = generate_openai_schema(TestModel)
|
|
anthropic_schema = generate_anthropic_schema(TestModel)
|
|
|
|
# Should reuse name and description from OpenAI schema
|
|
assert anthropic_schema["name"] == openai_schema["name"]
|
|
assert anthropic_schema["description"] == openai_schema["description"]
|
|
|
|
# But should have its own input_schema
|
|
assert "input_schema" in anthropic_schema
|
|
assert anthropic_schema["input_schema"] == TestModel.model_json_schema()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__])
|