""" Snapshot tests for the JSON schemas emitted for librarian tools. Ollama's OpenAI-compatible API mishandles anyOf[X, null] parameter schemas, so tool parameters must use empty-string/empty-list sentinels translated to None inside the tool (same pattern as the biographer tools, commit 9d7ce39). This test fails if a X | None parameter ever creeps back in. """ import pytest from pydantic_ai.tools import Tool from src.agents.librarian.tools import LIBRARIAN_TOOLS def _nullable_anyof_paths(schema: object, path: str = "") -> list[str]: """Recursively collect JSON-schema paths that are anyOf[..., null].""" offenders: list[str] = [] if isinstance(schema, dict): any_of = schema.get("anyOf") if isinstance(any_of, list) and any( isinstance(sub, dict) and sub.get("type") == "null" for sub in any_of ): offenders.append(path or "") for key, value in schema.items(): offenders.extend(_nullable_anyof_paths(value, f"{path}/{key}")) elif isinstance(schema, list): for i, item in enumerate(schema): offenders.extend(_nullable_anyof_paths(item, f"{path}[{i}]")) return offenders @pytest.mark.unit class TestLibrarianToolSchemas: """All registered librarian tools emit Ollama-safe parameter schemas.""" @pytest.mark.parametrize("tool_func", LIBRARIAN_TOOLS, ids=lambda f: f.__name__) def test_no_nullable_anyof_in_schema(self, tool_func): schema = Tool(tool_func).function_schema.json_schema offenders = _nullable_anyof_paths(schema) assert offenders == [], ( f"{tool_func.__name__} emits anyOf[..., null] at {offenders}; " "use empty-string/empty-list sentinels instead of X | None" )