diff --git a/api/.env.example b/api/.env.example index c7df8f0..0d6b52a 100644 --- a/api/.env.example +++ b/api/.env.example @@ -9,3 +9,10 @@ REPLICATE_API_TOKEN= # Port the proxy binds. compose / fly.io override this. PORT=8000 + +# Narrator model (charter §5 "the good model"). qwen3.x runs with thinking OFF. +OLLAMA_NARRATOR_MODEL=qwen3.5:latest +# Per-attempt model-call timeout (seconds). +OLLAMA_TIMEOUT_SECONDS=30 +# Optional: append call logs (JSON lines, §10) to this file instead of stdout. +CALL_LOG_PATH= diff --git a/api/conftest.py b/api/conftest.py new file mode 100644 index 0000000..38d7def --- /dev/null +++ b/api/conftest.py @@ -0,0 +1,21 @@ +"""Gate `@pytest.mark.live` tests (they hit a real Ollama) behind --run-live so +the default suite stays hermetic. Run the live layer with: pytest --run-live +""" + +import pytest + + +def pytest_addoption(parser): + parser.addoption( + "--run-live", action="store_true", default=False, + help="run tests marked @pytest.mark.live (require a real Ollama)", + ) + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--run-live"): + return + skip_live = pytest.mark.skip(reason="needs --run-live (real Ollama)") + for item in items: + if "live" in item.keywords: + item.add_marker(skip_live) diff --git a/api/pytest.ini b/api/pytest.ini index c038626..795b6eb 100644 --- a/api/pytest.ini +++ b/api/pytest.ini @@ -3,3 +3,5 @@ pythonpath = . testpaths = tests filterwarnings = ignore:Using `httpx` with `starlette.testclient` is deprecated +markers = + live: exercises a real Ollama; skipped unless --run-live diff --git a/api/tests/test_live_smoke.py b/api/tests/test_live_smoke.py new file mode 100644 index 0000000..c76066c --- /dev/null +++ b/api/tests/test_live_smoke.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + +import app.narrate as narrate + +VALID = json.loads((Path(__file__).parent / "fixtures" / "canon_log_valid.json").read_text()) + + +@pytest.mark.live +def test_narrator_returns_real_prose(): + """Hits the real Ollama (OLLAMA_BASE_URL). Proves the wire end-to-end: + prompt assembly → model → non-empty prose. Not asserted: tag content + (nondeterministic). Run with: pytest --run-live + """ + text = narrate.run(VALID) + assert isinstance(text, str) + assert len(text.strip()) > 40 # real prose, not a blip