Download tests/test_bankml_backend.py from PYTHAI/mindXtrain: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/PYTHAI/mindXtrain/resolve/main/tests/test_bankml_backend.py
- Command line
-
hf download hf://PYTHAI/mindXtrain/tests/test_bankml_backend.py
-
curl -L -o test_bankml_backend.py https://huggingface.co/PYTHAI/mindXtrain/resolve/main/tests/test_bankml_backend.py
13.7 kB
| """bankml operator backend: registry, env, receipts, typed refusals, SSE streaming, wiring. | |
| No network: every HTTP exchange goes through httpx.MockTransport. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import json | |
| import httpx | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from mindxtrain.governance import panel as P | |
| from mindxtrain.models.registry import ChatMessage, ChatRequest, build_backend, list_backends | |
| from mindxtrain.operator import app as operator_app | |
| from mindxtrain.operator.backends.bankml import ( | |
| DEFAULT_BANKML_BASE_URL, | |
| BankmlBackend, | |
| BankmlError, | |
| BankmlRefusal, | |
| bankml_root_url, | |
| ) | |
| _REAL_BANKML_REACHABLE = operator_app._bankml_reachable # captured before conftest patches it | |
| RECEIPT = { | |
| "bankml": "0.3.4", | |
| "engine": "native", | |
| "model_sha256": "6b64c748d96ad26fd72402299bd27b2ae82f489bd0469498dd18eb6054058266", | |
| "guard": "play", | |
| "prompt_tokens": 12, | |
| "completion_tokens": 3, | |
| "ttft_ms": 40, | |
| "wall_ms": 90, | |
| "response_sha256": "a" * 64, | |
| "request_sha256": "b" * 64, | |
| "signed": False, | |
| } | |
| def _req(stream: bool = False) -> ChatRequest: | |
| return ChatRequest( | |
| model="mindx-gen39", | |
| messages=[ChatMessage(role="user", content="Who are you?")], | |
| temperature=0.0, | |
| max_tokens=16, | |
| stream=stream, | |
| ) | |
| def _completion() -> dict[str, object]: | |
| return { | |
| "choices": [{"index": 0, "message": {"role": "assistant", "content": "I am mindX."}, | |
| "finish_reason": "stop"}], | |
| "model": "mindx-gen39-F16.gguf", | |
| "object": "chat.completion", | |
| "usage": {"completion_tokens": 3, "prompt_tokens": 12, "total_tokens": 15}, | |
| "bankml_receipt": RECEIPT, | |
| } | |
| # ---- registry + env ----------------------------------------------------------- | |
| def test_registered_and_built_by_name() -> None: | |
| assert "bankml" in list_backends() | |
| assert isinstance(build_backend("bankml"), BankmlBackend) | |
| def test_default_and_env_base_url(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.delenv("MINDXTRAIN_BANKML_BASE_URL", raising=False) | |
| assert BankmlBackend().base_url == DEFAULT_BANKML_BASE_URL | |
| monkeypatch.setenv("MINDXTRAIN_BANKML_BASE_URL", "http://127.0.0.1:9999/v1/") | |
| b = BankmlBackend() | |
| assert b.base_url == "http://127.0.0.1:9999/v1" | |
| assert bankml_root_url() == "http://127.0.0.1:9999" | |
| def test_never_sends_an_openai_key(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.setenv("MINDXTRAIN_OPENAI_API_KEY", "sk-should-not-leak") | |
| assert "Authorization" not in BankmlBackend()._headers() | |
| # ---- chat --------------------------------------------------------------------- | |
| def test_chat_keeps_the_receipt() -> None: | |
| seen: dict[str, object] = {} | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| seen["url"] = str(request.url) | |
| seen["body"] = json.loads(request.content) | |
| return httpx.Response(200, json=_completion()) | |
| b = BankmlBackend(seed=7, transport=httpx.MockTransport(handler)) | |
| resp = asyncio.run(b.chat(_req())) | |
| assert seen["url"] == "http://127.0.0.1:18093/v1/chat/completions" | |
| body = seen["body"] | |
| assert isinstance(body, dict) | |
| assert body["seed"] == 7 and body["temperature"] == 0.0 and body["stream"] is False | |
| assert resp.content == "I am mindX." | |
| assert resp.prompt_tokens == 12 and resp.completion_tokens == 3 | |
| assert resp.receipt == RECEIPT | |
| assert b.last_receipt == RECEIPT | |
| def test_chat_400_is_a_typed_refusal_and_is_not_retried() -> None: | |
| calls = {"n": 0} | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| calls["n"] += 1 | |
| return httpx.Response(400, text="mirostat: not reproduced; bankML reproduces llama.cpp's " | |
| "top-k, top-p, min-p and temperature") | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| with pytest.raises(BankmlRefusal) as info: | |
| asyncio.run(b.chat(_req())) | |
| assert info.value.status_code == 400 | |
| assert info.value.reason.startswith("mirostat: not reproduced") | |
| assert "bankml refused (400)" in str(info.value) | |
| assert calls["n"] == 1 | |
| def test_ollama_style_json_error_body_is_read() -> None: | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| return httpx.Response(400, json={"error": "tools: tool calling ... not reproduced yet"}) | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| with pytest.raises(BankmlRefusal) as info: | |
| asyncio.run(b.chat(_req())) | |
| assert info.value.reason.startswith("tools:") | |
| def test_non_400_errors_are_bankml_errors_not_refusals() -> None: | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| return httpx.Response(503, text="model changed since bankml verified it") | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| with pytest.raises(BankmlError) as info: | |
| asyncio.run(b.chat(_req())) | |
| assert not isinstance(info.value, BankmlRefusal) | |
| assert info.value.status_code == 503 | |
| # ---- stream ------------------------------------------------------------------- | |
| def _sse(*events: str) -> bytes: | |
| return "".join(f"data: {e}\n\n" for e in events).encode() | |
| def _collect(b: BankmlBackend) -> list[str]: | |
| async def run() -> list[str]: | |
| out: list[str] = [] | |
| async for tok in await b.stream_chat(_req(stream=True)): | |
| out.append(tok) | |
| return out | |
| return asyncio.run(run()) | |
| def test_stream_yields_tokens_and_keeps_the_final_receipt() -> None: | |
| def chunk(text: str) -> str: | |
| return json.dumps({"choices": [{"index": 0, "delta": {"content": text}, | |
| "finish_reason": None}]}) | |
| final = json.dumps({"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], | |
| "usage": {"completion_tokens": 2, "prompt_tokens": 12}}) | |
| body = _sse(chunk("I am"), chunk(" mindX."), final, | |
| json.dumps({"bankml_receipt": RECEIPT}), "[DONE]") | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| assert json.loads(request.content)["stream"] is True | |
| return httpx.Response(200, content=body, headers={"content-type": "text/event-stream"}) | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| assert _collect(b) == ["I am", " mindX."] | |
| assert b.last_receipt == RECEIPT | |
| def test_stream_400_raises_refusal() -> None: | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| return httpx.Response(400, text="repeat_penalty: not reproduced") | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| with pytest.raises(BankmlRefusal): | |
| _collect(b) | |
| def test_stream_error_event_raises() -> None: | |
| def handler(request: httpx.Request) -> httpx.Response: | |
| return httpx.Response(200, content=_sse(json.dumps({"error": "context full"}))) | |
| b = BankmlBackend(transport=httpx.MockTransport(handler)) | |
| with pytest.raises(BankmlError, match="context full"): | |
| _collect(b) | |
| # ---- operator wiring --------------------------------------------------------- | |
| def test_backend_kwargs_for_bankml(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.setenv("MINDXTRAIN_BANKML_BASE_URL", "http://127.0.0.1:18100/v1") | |
| assert operator_app.backend_kwargs("bankml") == {"base_url": "http://127.0.0.1:18100/v1"} | |
| def test_backend_kwargs_openai_strict_contract(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.delenv("MINDXTRAIN_OPENAI_BASE_URL", raising=False) | |
| with pytest.raises(KeyError): | |
| operator_app.backend_kwargs("openai_compat", strict=True) | |
| assert operator_app.backend_kwargs("openai_compat")["base_url"] == "" | |
| def test_bankml_probe_hits_the_identity_endpoint(monkeypatch: pytest.MonkeyPatch) -> None: | |
| seen: list[str] = [] | |
| class _Client: | |
| def __init__(self, *a: object, **k: object) -> None: ... | |
| def __enter__(self) -> _Client: | |
| return self | |
| def __exit__(self, *a: object) -> None: ... | |
| def get(self, url: str) -> httpx.Response: | |
| seen.append(url) | |
| return httpx.Response(200, json={"bankml": "0.3.4"}) | |
| monkeypatch.delenv("MINDXTRAIN_BANKML_BASE_URL", raising=False) | |
| monkeypatch.setattr(operator_app.httpx, "Client", _Client) | |
| assert _REAL_BANKML_REACHABLE() is True | |
| assert seen == ["http://127.0.0.1:18093/bankml"] | |
| def test_bankml_probe_false_on_connection_error(monkeypatch: pytest.MonkeyPatch) -> None: | |
| class _Client: | |
| def __init__(self, *a: object, **k: object) -> None: ... | |
| def __enter__(self) -> _Client: | |
| return self | |
| def __exit__(self, *a: object) -> None: ... | |
| def get(self, url: str) -> httpx.Response: | |
| raise httpx.ConnectError("refused") | |
| monkeypatch.setattr(operator_app.httpx, "Client", _Client) | |
| assert _REAL_BANKML_REACHABLE() is False | |
| def test_autodetect_order(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.delenv("MINDXTRAIN_BACKEND", raising=False) | |
| monkeypatch.delenv("AUTOMINDX_BACKEND", raising=False) | |
| monkeypatch.setattr(operator_app, "_bankml_reachable", lambda: True) | |
| # ollama first, always | |
| monkeypatch.setattr(operator_app, "_ollama_reachable", lambda: True) | |
| monkeypatch.setattr(operator_app, "_vllm_reachable", lambda: False) | |
| assert operator_app.resolve_backend_name() == "ollama" | |
| # vllm before bankml | |
| monkeypatch.setattr(operator_app, "_ollama_reachable", lambda: False) | |
| monkeypatch.setattr(operator_app, "_vllm_reachable", lambda: True) | |
| assert operator_app.resolve_backend_name() == "vllm" | |
| # bankml only when it is the one answering | |
| monkeypatch.setattr(operator_app, "_vllm_reachable", lambda: False) | |
| assert operator_app.resolve_backend_name() == "bankml" | |
| # nothing answering: unchanged fallback | |
| monkeypatch.setattr(operator_app, "_bankml_reachable", lambda: False) | |
| assert operator_app.resolve_backend_name() == "vllm" | |
| def test_health_dispatch(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.setattr(operator_app, "_bankml_reachable", lambda: True) | |
| monkeypatch.setattr(operator_app, "_bankml_first_model", lambda: "mindx-gen39-F16.gguf") | |
| assert operator_app.backend_reachable("bankml") is True | |
| assert operator_app.backend_first_model("bankml") == "mindx-gen39-F16.gguf" | |
| def test_operator_route_passes_refusal_through_as_400(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.setenv("MINDXTRAIN_BACKEND", "bankml") | |
| async def refuse(self: BankmlBackend, request: ChatRequest) -> object: | |
| raise BankmlRefusal("repeat_penalty: not reproduced") | |
| monkeypatch.setattr(BankmlBackend, "chat", refuse) | |
| client = TestClient(operator_app.app) | |
| r = client.post("/v1/chat/completions", json={ | |
| "model": "mindx-gen39", "messages": [{"role": "user", "content": "hi"}]}) | |
| assert r.status_code == 400 | |
| assert "repeat_penalty: not reproduced" in r.json()["detail"] | |
| def test_operator_route_returns_receipt(monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.setenv("MINDXTRAIN_BACKEND", "bankml") | |
| real_client = httpx.AsyncClient | |
| transport = httpx.MockTransport(lambda req: httpx.Response(200, json=_completion())) | |
| monkeypatch.setattr( | |
| "mindxtrain.operator.backends.bankml.httpx.AsyncClient", | |
| lambda **kw: real_client(timeout=kw.get("timeout"), transport=transport), | |
| ) | |
| client = TestClient(operator_app.app) | |
| r = client.post("/v1/chat/completions", json={ | |
| "model": "mindx-gen39", "messages": [{"role": "user", "content": "hi"}]}) | |
| assert r.status_code == 200 | |
| assert r.json()["receipt"]["model_sha256"] == RECEIPT["model_sha256"] | |
| # ---- governance panel env chain ----------------------------------------------- | |
| def test_panel_bankml_url_only_when_set_and_last(monkeypatch: pytest.MonkeyPatch) -> None: | |
| for e in ("MINDXTRAIN_OPENAI_BASE_URL", "MINDXTRAIN_VLLM_BASE_URL", | |
| "MINDXTRAIN_OLLAMA_BASE_URL", "MINDXTRAIN_BANKML_BASE_URL", "MINDXTRAIN_BACKEND"): | |
| monkeypatch.delenv(e, raising=False) | |
| assert P.resolve_chat_base_url() == "http://localhost:11434/v1" | |
| monkeypatch.setenv("MINDXTRAIN_BANKML_BASE_URL", "http://127.0.0.1:18093/v1") | |
| assert P.resolve_chat_base_url() == "http://127.0.0.1:18093/v1" | |
| monkeypatch.setenv("MINDXTRAIN_OLLAMA_BASE_URL", "http://localhost:11434/v1") | |
| assert P.resolve_chat_base_url() == "http://localhost:11434/v1" | |
| monkeypatch.setenv("MINDXTRAIN_BACKEND", "bankml") | |
| assert P.resolve_chat_base_url() == "http://127.0.0.1:18093/v1" | |
| # ---- console + HF Modelfile: neutral defaults are not sent ---------------------- | |
| def test_console_wire_options_omits_engine_defaults() -> None: | |
| from mindxtrain.ui import console | |
| sent = console.wire_options(dict(console.DEFAULTS)) | |
| for k in ("repeat_penalty", "presence_penalty", "frequency_penalty", "mirostat", | |
| "mirostat_tau", "mirostat_eta", "stop"): | |
| assert k not in sent, k | |
| assert sent["temperature"] == 0.7 and sent["top_k"] == 40 | |
| # a deliberate value is always sent — the engine then honours or refuses it, visibly | |
| deliberate = console.wire_options({"repeat_penalty": 1.3, "mirostat": 2, "mirostat_tau": 4.0}) | |
| assert deliberate == {"repeat_penalty": 1.3, "mirostat": 2, "mirostat_tau": 4.0} | |
| def test_hf_published_modelfile_repeat_penalty_is_optional() -> None: | |
| from mindxtrain.hf.extension import published_modelfile | |
| default = published_modelfile("You are mindX.") | |
| assert "PARAMETER repeat_penalty 1.3" in default # default unchanged | |
| assert default.endswith('PARAMETER stop "<|im_end|>"\n') | |
| assert "repeat_penalty" not in published_modelfile("You are mindX.", repeat_penalty=None) | |