CoolFace
Apppublic

srr84/agent-data-layer

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
test_answer_composer.py80 linesDownload Raw Back to tests
1"""Unit tests for M5/A9 answer_composer: the LLM job-2 nl_text-ONLY call site.2 3compose_nl() must (1) return the model's nl_text str verbatim and (2) CATCH the adapter's4ModelUnavailableError at the C-AS boundary, converting it to a typed ModelUnavailable5(stage=compose_nl) so no exception escapes the request path (INV-A6). It holds no fact: the6returned str is display-only (D-AI-2).7 8```text9spec traceability (audit map — safe to skip)10LLD §1 (M-answer_composer: compose_nl for nl_text ONLY; returns str), §3.1, §711TechSpec §4 (ModelUnavailable stage=compose_nl), §10 (no exception escapes)12Charter D-AI-1, D-AI-2 (return tainted), INV-A613```14"""15 16from __future__ import annotations17 18from typing import Sequence, Union19 20from agent_data_layer.answer_composer.answer_composer import compose_nl21from agent_data_layer.contracts.envelopes import ToolResult22from agent_data_layer.contracts.errors import ModelUnavailable, OutOfCoverage23from agent_data_layer.contracts.tools import PriceResult, ReturnSchema, ToolCall, ToolSpec24from agent_data_layer.local_model_adapter.local_model_adapter import ModelUnavailableError25 26_RESULT: ToolResult[ReturnSchema] = ToolResult(27    asserted_value=PriceResult(value=199), provenance=frozenset(), observed_at=None28)29 30 31class _FakeComposeProvider:32    """A provider whose compose_nl returns a canned sentence."""33 34    def __init__(self, sentence: str) -> None:35        self._sentence = sentence36 37    def select_tool(38        self, question: str, tool_specs: Sequence[ToolSpec]39    ) -> Union[ToolCall, OutOfCoverage]:40        return OutOfCoverage(reason="unused")41 42    def compose_nl(self, result: ToolResult[object]) -> str:43        return self._sentence44 45 46class _DownComposeProvider:47    """A provider whose compose_nl raises ModelUnavailableError (model-down on job-2)."""48 49    def select_tool(50        self, question: str, tool_specs: Sequence[ToolSpec]51    ) -> Union[ToolCall, OutOfCoverage]:52        return OutOfCoverage(reason="unused")53 54    def compose_nl(self, result: ToolResult[object]) -> str:55        raise ModelUnavailableError(56            ModelUnavailable(stage="compose_nl", detail="http_status=503")57        )58 59 60def test_compose_returns_nl_text_verbatim() -> None:61    """The model's nl_text is returned verbatim (display-only)."""62    out = compose_nl(_RESULT, _FakeComposeProvider("The price is $1.99."))63    assert out == "The price is $1.99."64 65 66def test_compose_catches_model_down_as_typed_error() -> None:67    """A compose-stage failure is caught -> typed ModelUnavailable(stage=compose_nl) (INV-A6)."""68    out = compose_nl(_RESULT, _DownComposeProvider())69    assert isinstance(out, ModelUnavailable)70    assert out.stage == "compose_nl"71    assert out.detail == "http_status=503"72 73 74def test_compose_never_raises_on_model_down() -> None:75    """No exception escapes the request path on a compose-stage model failure."""76    try:77        compose_nl(_RESULT, _DownComposeProvider())78    except Exception as exc:  # noqa: BLE00179        raise AssertionError(f"answer_composer leaked an exception: {exc!r}")80