# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors # # SPDX-License-Identifier: Apache-2.0 import json from dataclasses import dataclass from unittest.mock import AsyncMock import pytest from pydantic import BaseModel from ag2 import PromptedSchema, ResponseSchema, response_schema from ag2.response import ResponseProto class TestPromptedSchemaFromType: def test_from_primitive_type(self) -> None: schema = PromptedSchema(int) assert schema.name == "ResponseSchema" assert schema.json_schema is None assert schema.system_prompt is not None assert '"type": "integer"' in schema.system_prompt def test_from_dataclass(self) -> None: @dataclass class User: name: str age: int schema = PromptedSchema(User) assert schema.name == "User" assert '"name"' in schema.system_prompt assert '"age"' in schema.system_prompt def test_from_pydantic_model(self) -> None: class Item(BaseModel): title: str price: float schema = PromptedSchema(Item) assert schema.name == "Item" assert '"title"' in schema.system_prompt assert '"price"' in schema.system_prompt class TestPromptedSchemaFromResponseProto: def test_wraps_response_schema(self) -> None: inner = ResponseSchema(int, name="MyInt") schema = PromptedSchema(inner) assert schema.name == "MyInt" assert schema.description is inner.description assert '"type": "integer"' in schema.system_prompt def test_wraps_schema_with_description(self) -> None: inner = ResponseSchema(int, description="An integer value") schema = PromptedSchema(inner) assert schema.description == "An integer value" def test_inner_schema_preserved(self) -> None: inner = ResponseSchema(int, name="MyInt") original_schema = inner.json_schema schema = PromptedSchema(inner) assert schema._json_schema == original_schema assert schema.json_schema is None class TestSystemPrompt: def test_contains_json_schema(self) -> None: @dataclass class Config: debug: bool level: int schema = PromptedSchema(Config) prompt = schema.system_prompt assert prompt is not None # Should contain the full JSON schema inner_schema = ResponseSchema(Config).json_schema assert json.dumps(inner_schema, indent=2) in prompt def test_uses_default_template(self) -> None: schema = PromptedSchema(int) prompt = schema.system_prompt assert "You must respond with valid JSON" in prompt assert "Do not include any text" in prompt def test_custom_prompt_template(self) -> None: template = "Return JSON: ```{schema}```" schema = PromptedSchema(int, prompt_template=template) prompt = schema.system_prompt assert prompt.startswith("Return JSON: ```") def test_no_prompt_when_no_inner_schema(self) -> None: """If inner schema has no json_schema, system_prompt should be None.""" class NoSchemaProto(ResponseProto[str]): def __init__(self) -> None: self.name = "test" self.description = None self.json_schema = None async def validate(self, response, context, provider=None): return response schema = PromptedSchema(NoSchemaProto()) assert schema.system_prompt is None @pytest.mark.asyncio class TestValidation: async def test_validates_primitive(self) -> None: schema = PromptedSchema(int) context = AsyncMock() result = await schema.validate('{"data": 42}', context) assert result == 42 async def test_validates_dataclass(self) -> None: @dataclass class Point: x: float y: float schema = PromptedSchema(Point) context = AsyncMock() result = await schema.validate('{"x": 1.5, "y": 2.5}', context) assert result == Point(x=1.5, y=2.5) async def test_validates_pydantic_model(self) -> None: class User(BaseModel): name: str email: str schema = PromptedSchema(User) context = AsyncMock() result = await schema.validate('{"name": "Alice", "email": "a@b.com"}', context) assert isinstance(result, User) assert result.name == "Alice" assert result.email == "a@b.com" async def test_delegates_to_inner_response_schema(self) -> None: inner = ResponseSchema(int, name="MyInt") schema = PromptedSchema(inner) context = AsyncMock() result = await schema.validate('{"data": 42}', context) assert result == 42 async def test_delegates_to_inner_callable_schema(self) -> None: @response_schema def double(content: str) -> int: return int(content) * 2 schema = PromptedSchema(double) context = AsyncMock() result = await schema.validate("21", context) assert result == 42 async def test_validation_error_on_invalid_json(self) -> None: schema = PromptedSchema(int) context = AsyncMock() with pytest.raises(Exception): await schema.validate("not a number", context) async def test_callable_schema_validation_error(self) -> None: @response_schema def parse_int(content: str) -> int: return int(content) schema = PromptedSchema(parse_int) context = AsyncMock() with pytest.raises(Exception): await schema.validate("not a number", context)