Download tests/test_ollama_tool_call.py from INLEVEL9/Vons: direct link, hf CLI and curl.
- Browser
- Download file 35.4 kB
-
https://huggingface.co/INLEVEL9/Vons/resolve/main/tests/test_ollama_tool_call.py
- Command line
-
hf download hf://INLEVEL9/Vons/tests/test_ollama_tool_call.py
-
curl -L -o test_ollama_tool_call.py https://huggingface.co/INLEVEL9/Vons/resolve/main/tests/test_ollama_tool_call.py
35.4 kB
| """Synthetic tests for the Ollama native tool-call vertical slice. | |
| All tests use injected synthetic backends and canned HTTP responses via | |
| ``unittest.mock``. No real network calls, no real model weights, no restricted | |
| Mind2Web rows, no real Ollama daemon is contacted. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import tempfile | |
| import unittest | |
| from collections.abc import Mapping | |
| from pathlib import Path | |
| from types import SimpleNamespace | |
| from typing import Any | |
| from unittest.mock import MagicMock, patch | |
| from vons.contract import ( | |
| Backend, | |
| DecisionRequest, | |
| DecisionResponse, | |
| OptionProbability, | |
| QuestionAnswer, | |
| ResponseStatus, | |
| ) | |
| from vons.ollama import ( | |
| OllamaToolCallError, | |
| OllamaToolCallHost, | |
| _validate_loopback_host, | |
| tool_call_result_to_mapping, | |
| ) | |
| from vons.onnx_runtime import ( | |
| DecisionBackend, | |
| OnnxRuntimeBackend, | |
| OnnxRuntimeError, | |
| OnnxRuntimeUnavailableError, | |
| SyntheticAbstainBackend, | |
| SyntheticChoiceBackend, | |
| VerifiedBundle, | |
| _LocalTokenizer, | |
| ) | |
| def _canned_chat_payload(message_body: Mapping[str, Any]) -> Mapping[str, Any]: | |
| return {"message": message_body, "model": "synthetic", "done": True} | |
| def _vons_decide_tool_call(arguments: Mapping[str, Any]) -> dict[str, Any]: | |
| return { | |
| "role": "assistant", | |
| "content": "", | |
| "tool_calls": [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "vons_decide", | |
| "arguments": json.dumps(arguments) | |
| if isinstance(arguments, dict) | |
| else arguments, | |
| }, | |
| } | |
| ], | |
| } | |
| def _patch_post(host: OllamaToolCallHost, message_body: Mapping[str, Any]) -> None: | |
| host._post = MagicMock(return_value=_canned_chat_payload(message_body)) # type: ignore[method-assign] | |
| _VALID_REQUEST = { | |
| "state": "A weather tool is available. Should we call it?", | |
| "questions": [ | |
| { | |
| "id": "q-next", | |
| "type": "choice", | |
| "prompt": "Next action?", | |
| "options": ["call", "clarify", "confirm", "refuse"], | |
| } | |
| ], | |
| "backend": "direct", | |
| "seed": 7, | |
| } | |
| class _SpyBackend(DecisionBackend): | |
| """Test backend that records whether it was called and with what.""" | |
| backend_type = Backend.DIRECT | |
| def __init__( | |
| self, response: DecisionResponse | None = None, *, raise_on_call: Exception | None = None | |
| ) -> None: | |
| self.calls: list[DecisionRequest] = [] | |
| self._response = response | |
| self._raise = raise_on_call | |
| def decide(self, request: DecisionRequest) -> DecisionResponse: | |
| self._validate_request(request) | |
| self.calls.append(request) | |
| if self._raise is not None: | |
| raise self._raise | |
| if self._response is not None: | |
| return self._response | |
| return SyntheticChoiceBackend(choice_index=0).decide(request) | |
| class LoopbackValidationTests(unittest.TestCase): | |
| def test_accepts_localhost_variants(self) -> None: | |
| for url in ( | |
| "http://127.0.0.1:11434", | |
| "http://localhost:11434/api", | |
| "http://[::1]:11434/chat", | |
| ): | |
| _validate_loopback_host(url) # must not raise | |
| def test_rejects_non_loopback(self) -> None: | |
| for url in ( | |
| "http://example.com:11434", | |
| "https://192.168.1.1/api/chat", | |
| "http://0.0.0.0:11434", | |
| "http://[2001:db8::1]:11434", | |
| "ftp://127.0.0.1:11434", | |
| ): | |
| with self.assertRaises(OllamaToolCallError): | |
| _validate_loopback_host(url) | |
| def test_empty_or_invalid_urls(self) -> None: | |
| for url in ("", "not-a-url", "127.0.0.1"): | |
| with self.assertRaises(OllamaToolCallError): | |
| _validate_loopback_host(url) # type: ignore[arg-type] | |
| class HostInitTests(unittest.TestCase): | |
| def test_requires_loopback_host(self) -> None: | |
| backend = SyntheticAbstainBackend() | |
| with self.assertRaises(OllamaToolCallError): | |
| OllamaToolCallHost("http://example.com:11434", backend) | |
| def test_requires_decision_backend_instance(self) -> None: | |
| with self.assertRaises(OllamaToolCallError): | |
| OllamaToolCallHost("http://127.0.0.1:11434", "not a backend") # type: ignore[arg-type] | |
| def test_valid_init(self) -> None: | |
| backend = SyntheticAbstainBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434/", backend) | |
| self.assertEqual(host.host, "http://127.0.0.1:11434") | |
| self.assertIs(host.backend, backend) | |
| class ToolCallRoundTripTests(unittest.TestCase): | |
| def test_successful_tool_call_invokes_backend(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| messages = [{"role": "user", "content": "pick one"}] | |
| result = host.chat_with_tools("synthetic-model", messages) | |
| self.assertIsNone(result.error) | |
| self.assertIsNone(result.error_id) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 1) | |
| request = backend.calls[0] | |
| self.assertEqual(len(request.questions), 1) | |
| self.assertEqual(request.questions[0].id, "q-next") | |
| self.assertEqual(request.backend, Backend.DIRECT) | |
| self.assertEqual(request.seed, 7) | |
| # Reconciliation: backend answer IDs match request IDs exactly, positionally | |
| self.assertEqual(result.backend_result.answers[0].question_id, "q-next") | |
| def test_accepts_observed_ollama_tool_call_shape(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| observed_message = { | |
| "role": "assistant", | |
| "content": "", | |
| "thinking": "synthetic reasoning metadata", | |
| "tool_calls": [ | |
| { | |
| "id": "call_synthetic", | |
| "function": { | |
| "index": 0, | |
| "name": "vons_decide", | |
| "arguments": _VALID_REQUEST, | |
| }, | |
| } | |
| ], | |
| } | |
| _patch_post(host, observed_message) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNone(result.error) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertEqual(len(backend.calls), 1) | |
| def test_invalid_ollama_function_index_blocks_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| observed_message = { | |
| "role": "assistant", | |
| "content": "", | |
| "tool_calls": [ | |
| { | |
| "id": "call_synthetic", | |
| "function": { | |
| "index": -1, | |
| "name": "vons_decide", | |
| "arguments": _VALID_REQUEST, | |
| }, | |
| } | |
| ], | |
| } | |
| _patch_post(host, observed_message) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_raw_and_validated_separate(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| # Raw Ollama response is preserved as-is (contains the exact tool_call shape) | |
| self.assertIsNotNone(result.raw_ollama_response) | |
| raw_message = ( | |
| result.raw_ollama_response.get("message", {}) | |
| if isinstance(result.raw_ollama_response, dict) | |
| else {} | |
| ) | |
| self.assertEqual(len(raw_message.get("tool_calls", [])), 1) | |
| # Validated backend result is a separate, typed DecisionResponse | |
| self.assertIsNotNone(result.backend_result) | |
| self.assertIsInstance(result.backend_result, DecisionResponse) | |
| self.assertIsNot(result.raw_ollama_response, result.backend_result) | |
| def test_model_text_reply_without_tool_call_is_not_an_error(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, {"role": "assistant", "content": "I think you should call."}) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertFalse(result.tool_invoked) | |
| self.assertIsNone(result.backend_result) | |
| self.assertIsNone(result.error) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_wrong_tool_name_blocks_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| wrong_tool = { | |
| "role": "assistant", | |
| "content": "", | |
| "tool_calls": [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "delete_everything", | |
| "arguments": json.dumps({"scope": "all"}), | |
| }, | |
| } | |
| ], | |
| } | |
| _patch_post(host, wrong_tool) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) # model tried to invoke a tool | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 0) # backend never called | |
| def test_malformed_arguments_blocks_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| missing_required = {"state": "", "questions": []} # empty + no questions | |
| _patch_post(host, _vons_decide_tool_call(missing_required)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_unknown_root_argument_blocks_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| arguments = {**_VALID_REQUEST, "_authorization": "approve"} | |
| _patch_post(host, _vons_decide_tool_call(arguments)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_unknown_question_argument_blocks_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| question = {**_VALID_REQUEST["questions"][0], "execute": True} | |
| arguments = {**_VALID_REQUEST, "questions": [question]} | |
| _patch_post(host, _vons_decide_tool_call(arguments)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_oversized_tool_arguments_block_backend_call(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| arguments = {**_VALID_REQUEST, "state": "x" * 70_000} | |
| _patch_post(host, _vons_decide_tool_call(arguments)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_invalid_message_shape_is_rejected_before_post(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| host._post = MagicMock(side_effect=AssertionError("network must not be reached")) # type: ignore[method-assign] | |
| result = host.chat_with_tools("synthetic-model", [{"role": ["user"], "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| host._post.assert_not_called() | |
| def test_duplicate_question_ids_block_backend(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| dup_request = { | |
| "state": "state", | |
| "questions": [ | |
| {"id": "same", "type": "choice", "prompt": "first", "options": ["a", "b"]}, | |
| {"id": "same", "type": "choice", "prompt": "second", "options": ["c", "d"]}, | |
| ], | |
| } | |
| _patch_post(host, _vons_decide_tool_call(dup_request)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 0) | |
| def test_backend_returning_mismatched_ids_is_rejected(self) -> None: | |
| """The backend must return answers whose IDs match the request order exactly. | |
| This guards against a compromised or buggy backend reordering or dropping | |
| answers so a client cannot silently misattribute choices to questions. | |
| """ | |
| # Build a response with the WRONG question_id | |
| bad_answer = QuestionAnswer( | |
| question_id="q-SURPRISE", | |
| choice="call", | |
| probabilities=(OptionProbability(option="call", probability=1.0),), | |
| confidence=1.0, | |
| status=ResponseStatus.OK, | |
| ) | |
| bad_response = DecisionResponse( | |
| answers=(bad_answer,), | |
| backend=Backend.DIRECT, | |
| model_id="buggy-backend", | |
| ) | |
| backend = _SpyBackend(response=bad_response) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 1) # backend DID run, but its output rejected | |
| def test_backend_raising_is_sanitized(self) -> None: | |
| backend = _SpyBackend(raise_on_call=RuntimeError("weights corrupted, DB password leaked")) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| # Sanitized error must NOT contain the leaked exception text | |
| error_str = str(result.error) + " " + json.dumps(result.error.sanitized()) | |
| self.assertNotIn("DB password", error_str) | |
| self.assertNotIn("weights corrupted", error_str) | |
| def test_empty_messages_list_is_error(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| host._post = MagicMock(side_effect=AssertionError("network must not be reached")) # type: ignore[method-assign] | |
| result = host.chat_with_tools("synthetic-model", []) | |
| self.assertIsNotNone(result.error) | |
| self.assertFalse(result.tool_invoked) | |
| host._post.assert_not_called() | |
| def test_invalid_model_arg(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| result = host.chat_with_tools("", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertEqual(result.model, "") | |
| def test_result_mapping_preserves_raw_separate_from_validated(self) -> None: | |
| backend = SyntheticChoiceBackend(choice_index=0) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| mapped = tool_call_result_to_mapping(result) | |
| self.assertEqual(mapped["model"], "synthetic-model") | |
| self.assertTrue(mapped["tool_invoked"]) | |
| self.assertIsNone(mapped["error"]) | |
| backend_result = mapped["backend_result"] | |
| self.assertIsInstance(backend_result, dict) | |
| self.assertEqual(backend_result["answers"][0]["question_id"], "q-next") | |
| self.assertEqual(mapped["raw_ollama_response"], result.raw_ollama_response) | |
| self.assertIsNot(mapped["raw_ollama_response"], mapped["backend_result"]) | |
| def test_response_with_empty_ok_probabilities_is_rejected(self) -> None: | |
| answer = QuestionAnswer( | |
| question_id="q-next", | |
| choice="call", | |
| probabilities=(), | |
| confidence=0.8, | |
| status=ResponseStatus.OK, | |
| ) | |
| backend = _SpyBackend( | |
| response=DecisionResponse( | |
| answers=(answer,), backend=Backend.DIRECT, model_id="synthetic" | |
| ) | |
| ) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| def test_response_with_non_json_metadata_is_rejected(self) -> None: | |
| response = SyntheticChoiceBackend().decide(DecisionRequest.from_mapping(_VALID_REQUEST)) | |
| object.__setattr__(response, "metadata", {"unsafe": object()}) | |
| backend = _SpyBackend(response=response) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| def test_abstain_backend_preserves_reason_through_host(self) -> None: | |
| EXACT_REASON = "synthetic backend requested abstention" | |
| backend = SyntheticAbstainBackend(abstain_reason=EXACT_REASON) | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| _patch_post(host, _vons_decide_tool_call(_VALID_REQUEST)) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertIsNone(result.error) | |
| self.assertIsNotNone(result.backend_result) | |
| answer = result.backend_result.answers[0] | |
| self.assertEqual(answer.status, ResponseStatus.ABSTAIN) | |
| self.assertIsNone(answer.choice) | |
| self.assertEqual(answer.abstain_reason, EXACT_REASON) | |
| def test_multiple_tool_calls_rejected(self) -> None: | |
| backend = _SpyBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| double = { | |
| "role": "assistant", | |
| "content": "", | |
| "tool_calls": [ | |
| { | |
| "type": "function", | |
| "function": {"name": "vons_decide", "arguments": json.dumps(_VALID_REQUEST)}, | |
| }, | |
| { | |
| "type": "function", | |
| "function": {"name": "vons_decide", "arguments": json.dumps(_VALID_REQUEST)}, | |
| }, | |
| ], | |
| } | |
| _patch_post(host, double) | |
| result = host.chat_with_tools("synthetic-model", [{"role": "user", "content": "x"}]) | |
| self.assertTrue(result.tool_invoked) | |
| self.assertIsNotNone(result.error) | |
| self.assertIsNone(result.backend_result) | |
| self.assertEqual(len(backend.calls), 0) | |
| class RedirectEscapeTests(unittest.TestCase): | |
| def test_host_validates_url_before_each_http_call(self) -> None: | |
| backend = SyntheticAbstainBackend() | |
| host = OllamaToolCallHost("http://127.0.0.1:11434", backend) | |
| # Simulate opener.open raising an error - we already know the host | |
| # validates host on the way in via constructor. Now confirm the | |
| # per-call _post path also re-validates by replacing host.host with | |
| # a remote host and calling _post directly. | |
| original_host = host.host | |
| try: | |
| host.host = "http://evil.example.com:11434" # type: ignore[misc] | |
| with self.assertRaises(OllamaToolCallError): | |
| host._post("/api/chat", {"model": "x"}) | |
| finally: | |
| host.host = original_host | |
| def test_non_loopback_redirect_is_blocked_by_handler(self) -> None: | |
| """`_LoopbackRedirectHandler` rejects redirects leaving loopback. | |
| We do not actually execute the redirect handler through an HTTP server | |
| in synthetic fixtures; we confirm the handler class exists and the | |
| helper `_validate_loopback_host` rejects a redirect-style URL that | |
| would point away from loopback. | |
| """ | |
| from vons.ollama import _LoopbackRedirectHandler | |
| self.assertTrue(callable(_LoopbackRedirectHandler.redirect_request)) | |
| with self.assertRaises(OllamaToolCallError): | |
| _validate_loopback_host("http://10.0.0.5:11434/redirected") | |
| with self.assertRaises(OllamaToolCallError): | |
| _validate_loopback_host("https://remote.internal:8443/api/chat") | |
| class SyntheticBackendContractTests(unittest.TestCase): | |
| def test_synthetic_abstain_requires_reason(self) -> None: | |
| backend = SyntheticAbstainBackend("my reason") | |
| request = DecisionRequest.from_mapping(_VALID_REQUEST) | |
| response = backend.decide(request) | |
| for answer in response.answers: | |
| self.assertEqual(answer.abstain_reason, "my reason") | |
| self.assertIsNone(answer.choice) | |
| def test_synthetic_choice_preserves_position(self) -> None: | |
| backend = SyntheticChoiceBackend(choice_index=1) | |
| request = DecisionRequest.from_mapping(_VALID_REQUEST) | |
| response = backend.decide(request) | |
| self.assertEqual(response.answers[0].choice, "clarify") # index 1 | |
| probs = {p.option: p.probability for p in response.answers[0].probabilities} | |
| self.assertAlmostEqual(sum(probs.values()), 1.0, places=5) | |
| def test_synthetic_choice_out_of_range_is_error(self) -> None: | |
| backend = SyntheticChoiceBackend(choice_index=99) | |
| request = DecisionRequest.from_mapping(_VALID_REQUEST) | |
| with self.assertRaises(OnnxRuntimeError): | |
| backend.decide(request) | |
| class _FakeEncoding: | |
| def __init__(self, ids: list[int]) -> None: | |
| self.ids = ids | |
| self.attention_mask = [1] * len(ids) | |
| self.token_type_ids = [0] * len(ids) | |
| class _FakeTokenizer: | |
| pad_token_id = 0 | |
| cls_token_id = 1 | |
| def encode(self, text: str, **_kwargs: Any) -> _FakeEncoding: | |
| return _FakeEncoding([1, *range(3, 3 + len(text.split())), 2]) | |
| class _FakeOnnxSession: | |
| def __init__(self, backend: Backend, *, bad_shape: bool = False) -> None: | |
| self.backend = backend | |
| self.bad_shape = bad_shape | |
| self.feeds: list[dict[str, Any]] = [] | |
| self.input_names = ["input_ids", "attention_mask", "token_type_ids", "option_mask"] | |
| if backend is Backend.DIFFUSION: | |
| self.input_names.append("initial_noise") | |
| self.output_names = [ | |
| "logits" if backend is Backend.DIRECT else "scores", | |
| "answerability", | |
| ] | |
| def get_inputs(self) -> list[SimpleNamespace]: | |
| return [SimpleNamespace(name=name) for name in self.input_names] | |
| def get_outputs(self) -> list[SimpleNamespace]: | |
| return [SimpleNamespace(name=name) for name in self.output_names] | |
| def run(self, output_names: list[str], feed: dict[str, Any]) -> list[Any]: | |
| import numpy as np | |
| self.feeds.append({key: np.array(value, copy=True) for key, value in feed.items()}) | |
| slots = feed["option_mask"].shape[1] | |
| width = slots + 1 if self.bad_shape else slots | |
| scores = np.linspace(2.0, -2.0, width, dtype=np.float32)[None, :] | |
| values = { | |
| self.output_names[0]: scores, | |
| "answerability": np.array([8.0], dtype=np.float32), | |
| } | |
| return [values[name] for name in output_names] | |
| def _runtime_backend( | |
| backend_type: Backend, *, bad_shape: bool = False | |
| ) -> tuple[OnnxRuntimeBackend, _FakeOnnxSession]: | |
| inputs = ["input_ids", "attention_mask", "token_type_ids", "option_mask"] | |
| if backend_type is Backend.DIFFUSION: | |
| inputs.append("initial_noise") | |
| outputs = ["logits" if backend_type is Backend.DIRECT else "scores", "answerability"] | |
| manifest = { | |
| "metadata": { | |
| "backend": backend_type.value, | |
| "option_count": 4, | |
| "sequence_length": 64, | |
| "input_names": inputs, | |
| "output_names": outputs, | |
| "diffusion_conditioning": "token_cross_attention", | |
| }, | |
| "files": [], | |
| } | |
| bundle = VerifiedBundle( | |
| root=Path("."), | |
| manifest_path=Path("bundle-manifest-v1.json"), | |
| manifest=manifest, | |
| model_id="a" * 64, | |
| ) | |
| session = _FakeOnnxSession(backend_type, bad_shape=bad_shape) | |
| return ( | |
| OnnxRuntimeBackend(bundle, tokenizer=_FakeTokenizer(), session=session), | |
| session, | |
| ) | |
| class OnnxRuntimeExecutionTests(unittest.TestCase): | |
| def test_local_tokenizer_adapter_exposes_token_ids_for_budget_check(self) -> None: | |
| class RawTokenizer: | |
| max_length: int | None = None | |
| def enable_truncation(self, *, max_length: int) -> None: | |
| self.max_length = max_length | |
| def no_truncation(self) -> None: | |
| self.max_length = None | |
| def encode(self, text: str, *, add_special_tokens: bool) -> SimpleNamespace: | |
| ids = [1, *range(2, 2 + len(text.split())), 3] | |
| return SimpleNamespace( | |
| ids=ids, attention_mask=[1] * len(ids), type_ids=[0] * len(ids) | |
| ) | |
| tokenizer = _LocalTokenizer(RawTokenizer(), pad_token_id=0, cls_token_id=1) | |
| encoded = tokenizer.encode("one two three", max_length=32) | |
| self.assertGreater(len(encoded.ids), 3) | |
| self.assertEqual(len(encoded.ids), len(encoded.attention_mask)) | |
| self.assertIsNone(tokenizer._tokenizer.max_length) | |
| def test_executes_direct_session_and_returns_validated_choice(self) -> None: | |
| backend, session = _runtime_backend(Backend.DIRECT) | |
| request = DecisionRequest.from_mapping(_VALID_REQUEST) | |
| result = backend.decide(request) | |
| self.assertEqual(result.backend, Backend.DIRECT) | |
| self.assertEqual(result.answers[0].question_id, "q-next") | |
| self.assertEqual(result.answers[0].choice, "call") | |
| self.assertEqual(session.feeds[0]["input_ids"].shape[:2], (1, 4)) | |
| self.assertNotIn("initial_noise", session.feeds[0]) | |
| def test_executes_diffusion_session_with_seeded_noise(self) -> None: | |
| backend, session = _runtime_backend(Backend.DIFFUSION) | |
| request = DecisionRequest.from_mapping({**_VALID_REQUEST, "backend": "diffusion"}) | |
| first = backend.decide(request) | |
| second = backend.decide(request) | |
| import numpy as np | |
| self.assertEqual(first.answers[0].choice, "call") | |
| self.assertEqual(second.answers[0].choice, "call") | |
| self.assertEqual(session.feeds[0]["initial_noise"].shape, (1, 4)) | |
| np.testing.assert_array_equal( | |
| session.feeds[0]["initial_noise"], session.feeds[1]["initial_noise"] | |
| ) | |
| def test_injected_session_must_match_bundle_contract(self) -> None: | |
| backend, session = _runtime_backend(Backend.DIRECT) | |
| session.input_names.remove("option_mask") | |
| with self.assertRaises(OnnxRuntimeError): | |
| backend.decide(DecisionRequest.from_mapping(_VALID_REQUEST)) | |
| def test_invalid_onnx_score_shape_fails_closed(self) -> None: | |
| backend, _session = _runtime_backend(Backend.DIRECT, bad_shape=True) | |
| with self.assertRaisesRegex(OnnxRuntimeError, "invalid score dimensions"): | |
| backend.decide(DecisionRequest.from_mapping(_VALID_REQUEST)) | |
| class BundleLoadTests(unittest.TestCase): | |
| def test_missing_directory_is_error(self) -> None: | |
| with tempfile.TemporaryDirectory() as td: | |
| missing = Path(td) / "nope" | |
| with self.assertRaises(OnnxRuntimeError): | |
| VerifiedBundle.load(missing) | |
| def test_directory_without_manifest_is_error(self) -> None: | |
| with tempfile.TemporaryDirectory() as td, self.assertRaises(OnnxRuntimeError): | |
| VerifiedBundle.load(td) | |
| def test_directory_with_manifest_missing_graph_fails_verification(self) -> None: | |
| with tempfile.TemporaryDirectory() as td: | |
| root = Path(td) | |
| # A manifest with a bogus file inventory will fail verify_bundle_manifest | |
| bad_manifest = { | |
| "schema_version": "vons.bundle.manifest/v1", | |
| "manifest": {"path": "bundle-manifest.json"}, | |
| "files": [ | |
| {"path": "nope.onnx", "role": "model_graph", "bytes": 1, "sha256": "0" * 64} | |
| ], | |
| "summary": { | |
| "model_asset_bytes": 1, | |
| "manifest_bytes": 0, | |
| "complete_download_bytes": 0, | |
| "within_model_asset_limit": True, | |
| "runtime_loaded_file_count": 0, | |
| }, | |
| } | |
| (root / "bundle-manifest.json").write_text(json.dumps(bad_manifest), encoding="utf-8") | |
| with self.assertRaises(OnnxRuntimeError): | |
| VerifiedBundle.load(root) | |
| class SanitizedErrorTests(unittest.TestCase): | |
| def test_error_id_is_12_hex_chars(self) -> None: | |
| err = OllamaToolCallError() | |
| self.assertRegex(err.error_id, r"^[0-9a-f]{12}$") | |
| self.assertEqual(len(err.error_id), 12) | |
| def test_two_errors_have_different_ids(self) -> None: | |
| a = OllamaToolCallError() | |
| b = OllamaToolCallError() | |
| self.assertNotEqual(a.error_id, b.error_id) | |
| def test_sanitized_payload_omits_cause_text(self) -> None: | |
| err = OllamaToolCallError() | |
| san = err.sanitized() | |
| self.assertEqual(san["error"], "ollama_tool_call_failed") | |
| self.assertIn("error_id", san) | |
| self.assertEqual(len(san), 2) | |
| class CLISmokeTests(unittest.TestCase): | |
| def test_cli_parser_accepts_ollama_tool_call_command(self) -> None: | |
| from vons.cli import _parser | |
| parser = _parser() | |
| args = parser.parse_args( | |
| [ | |
| "ollama-tool-call", | |
| "--model", | |
| "qwen3.8:27b-mlx", | |
| "--synthetic", | |
| "abstain", | |
| "--input", | |
| "ignored.json", | |
| ] | |
| ) | |
| self.assertEqual(args.command, "ollama-tool-call") | |
| self.assertEqual(args.model, "qwen3.8:27b-mlx") | |
| self.assertEqual(args.synthetic, "abstain") | |
| def test_cli_requires_backend_flag(self) -> None: | |
| from vons.cli import main as cli_main | |
| with tempfile.TemporaryDirectory() as td: | |
| input_path = Path(td) / "in.json" | |
| input_path.write_text(json.dumps({"messages": []}), encoding="utf-8") | |
| rc = cli_main(["ollama-tool-call", "--model", "qwen", "--input", str(input_path)]) | |
| # Exit code 2 means neither --bundle nor --synthetic supplied | |
| self.assertEqual(rc, 2) | |
| def test_cli_missing_messages_is_error(self) -> None: | |
| from vons.cli import main as cli_main | |
| with tempfile.TemporaryDirectory() as td: | |
| input_path = Path(td) / "in.json" | |
| input_path.write_text("{}", encoding="utf-8") | |
| rc = cli_main( | |
| [ | |
| "ollama-tool-call", | |
| "--model", | |
| "qwen", | |
| "--synthetic", | |
| "abstain", | |
| "--input", | |
| str(input_path), | |
| ] | |
| ) | |
| self.assertEqual(rc, 3) | |
| def test_tool_call_input_reader_enforces_byte_limit(self) -> None: | |
| from vons.cli import _MAX_TOOL_CALL_INPUT_BYTES, _read_tool_call_input | |
| with tempfile.TemporaryDirectory() as td: | |
| path = Path(td) / "oversized.json" | |
| path.write_bytes(b" " * (_MAX_TOOL_CALL_INPUT_BYTES + 1)) | |
| with self.assertRaisesRegex(ValueError, "size limit"): | |
| _read_tool_call_input(path) | |
| def test_tool_call_input_reader_rejects_unknown_root_keys(self) -> None: | |
| from vons.cli import _read_tool_call_input | |
| with tempfile.TemporaryDirectory() as td: | |
| path = Path(td) / "input.json" | |
| path.write_text('{"messages": [], "extra": true}', encoding="utf-8") | |
| with self.assertRaises(TypeError): | |
| _read_tool_call_input(path) | |
| def test_cli_runtime_dependency_preflight_happens_before_host(self) -> None: | |
| from vons.cli import main as cli_main | |
| with tempfile.TemporaryDirectory() as td: | |
| input_path = Path(td) / "input.json" | |
| input_path.write_text( | |
| json.dumps({"messages": [{"role": "user", "content": "synthetic"}]}), | |
| encoding="utf-8", | |
| ) | |
| bundle = VerifiedBundle( | |
| root=Path(td), | |
| manifest_path=Path(td) / "bundle-manifest-v1.json", | |
| manifest={ | |
| "metadata": { | |
| "backend": "direct", | |
| "option_count": 4, | |
| "sequence_length": 64, | |
| "input_names": [ | |
| "input_ids", | |
| "attention_mask", | |
| "token_type_ids", | |
| "option_mask", | |
| ], | |
| "output_names": ["logits", "answerability"], | |
| }, | |
| "files": [], | |
| }, | |
| model_id="a" * 64, | |
| ) | |
| with ( | |
| patch("vons.cli.VerifiedBundle.load", return_value=bundle), | |
| patch.object( | |
| OnnxRuntimeBackend, | |
| "prepare", | |
| side_effect=OnnxRuntimeUnavailableError("missing synthetic dependency"), | |
| ), | |
| patch("vons.cli.OllamaToolCallHost") as host_factory, | |
| ): | |
| rc = cli_main( | |
| [ | |
| "ollama-tool-call", | |
| "--model", | |
| "synthetic", | |
| "--bundle", | |
| td, | |
| "--input", | |
| str(input_path), | |
| ] | |
| ) | |
| self.assertEqual(rc, 16) | |
| host_factory.assert_not_called() | |
| if __name__ == "__main__": | |
| unittest.main() | |