"""Live tests for :class:`OllamaClient` — gated on ``IX_TEST_OLLAMA=1``. Never runs in CI (Forgejo runner has no LAN access to Ollama). Run locally:: IX_TEST_OLLAMA=1 uv run pytest tests/live/test_ollama_client_live.py -v Assumes the Ollama server at ``http://192.168.68.42:11434`` already has ``qwen3:14b`` pulled. """ from __future__ import annotations import os import pytest from ix.genai.ollama_client import OllamaClient from ix.use_cases.bank_statement_header import BankStatementHeader pytestmark = [ pytest.mark.live, pytest.mark.skipif( os.environ.get("IX_TEST_OLLAMA") != "1", reason="live: IX_TEST_OLLAMA=1 required", ), ] _OLLAMA_URL = "http://192.168.68.42:11434" _MODEL = "qwen3:14b" async def test_structured_output_round_trip() -> None: """Real Ollama returns a parsed BankStatementHeader instance.""" client = OllamaClient(base_url=_OLLAMA_URL, per_call_timeout_s=300.0) result = await client.invoke( request_kwargs={ "model": _MODEL, "messages": [ { "role": "system", "content": ( "You extract bank statement header fields. " "Return valid JSON matching the given schema. " "Do not invent values." ), }, { "role": "user", "content": ( "Bank: Deutsche Kreditbank (DKB)\n" "Currency: EUR\n" "IBAN: DE89370400440532013000\n" "Period: 2025-01-01 to 2025-01-31" ), }, ], }, response_schema=BankStatementHeader, ) assert isinstance(result.parsed, BankStatementHeader) assert isinstance(result.parsed.bank_name, str) assert result.parsed.bank_name # non-empty assert isinstance(result.parsed.currency, str) assert result.model_name # server echoes a model name async def test_selfcheck_ok_against_real_server() -> None: """``selfcheck`` returns ``ok`` when the target model is pulled.""" client = OllamaClient(base_url=_OLLAMA_URL, per_call_timeout_s=5.0) assert await client.selfcheck(expected_model=_MODEL) == "ok"