setup models

This commit is contained in:
Marcos Lohmann
2026-10-07 13:43:09 -03:00
parent aeee6c7de1
commit 84ea9a5307
75 changed files with 5093 additions and 58 deletions

71
tests/test_canonical.py Normal file
View File

@@ -0,0 +1,71 @@
from dmb.canonical import fit_state, serialize_state, to_jev_request
STATE = {
"active_agent": "agente_a",
"suspended_sessions": ["agente_b"],
"recent_turns": [
{"role": "user", "text": "primeiro turno"},
{"role": "agent", "agent_id": "agente_a", "text": "resposta do agente"},
{"role": "user", "text": "segundo turno"},
],
}
EXAMPLE = {
"id": "conv-1-t3-D2", "group_id": "conv-1", "task": "D2", "state": STATE,
"utterance": "pode ser o segundo",
"question": "Como o orquestrador deve tratar este turno?", "question_type": "choice",
"options": {"continuar_agente_ativo": "Continua a tarefa em curso",
"retomar_sessao:agente_b": "Volta ao assunto do agente_b",
"iniciar_novo_agente": "Intenção nova",
"desambiguar": "Não dá para decidir"},
"gold_label": "continuar_agente_ativo",
}
def words(text):
return len(text.split())
def test_serialization_is_deterministic_and_ends_with_utterance():
text = serialize_state(STATE, "pode ser o segundo", 2)
assert text == serialize_state(STATE, "pode ser o segundo", 2)
assert text.endswith("Mensagem: pode ser o segundo")
assert "primeiro turno" not in text and "segundo turno" in text
assert "Agente ativo: agente_a" in text
def test_zero_turns_has_no_history():
assert "Turnos anteriores" not in serialize_state(STATE, "x", 0)
def test_fit_drops_oldest_turns_first():
full = words(serialize_state(STATE, "pode ser o segundo", 3))
text, info = fit_state(STATE, "pode ser o segundo", 3, full - 1, words)
assert info["turns_used"] < 3 and info["truncated"]
assert text.endswith("Mensagem: pode ser o segundo")
def test_fit_cuts_utterance_tail_only_as_last_resort():
long = "palavra " * 200
text, info = fit_state(STATE, long, 2, 30, words)
assert info["turns_used"] == 0 and info["utterance_chars_kept"] < len(long)
assert words(text) <= 30
def test_to_jev_request_keeps_option_order():
request, info = to_jev_request(EXAMPLE, "m", n_turns=2)
q = request["questions"]["D2"]
assert list(q["criteria"]) == list(EXAMPLE["options"])
assert q["type"] == "choice" and info["serialization"] == "state-text-v1"
def test_multi_question_example():
example = dict(EXAMPLE)
for key in ("question", "question_type", "options", "gold_label"):
example.pop(key)
clara = {"true": "sim", "false": "não"}
example["questions"] = [
{"name": "pede_x", "question": "Pede X?", "question_type": "noul", "options": clara, "gold_label": "true"},
{"name": "pede_y", "question": "Pede Y?", "question_type": "noul", "options": clara, "gold_label": "false"},
]
request, _ = to_jev_request(example, "m", n_turns=0)
assert list(request["questions"]) == ["pede_x", "pede_y"]

53
tests/test_contract.py Normal file
View File

@@ -0,0 +1,53 @@
import json
from pathlib import Path
import pytest
from dmb import jev
FIXTURES = Path(__file__).resolve().parents[1] / "contracts" / "jev" / "fixtures"
CASES = json.loads((FIXTURES / "conformance.json").read_text())["cases"]
@pytest.mark.parametrize("case", CASES, ids=[c["name"] for c in CASES])
def test_request_fixtures(case):
if case["expect"] == "ok":
jev.validate_request(case["request"])
else:
with pytest.raises(jev.InvalidRequest):
jev.validate_request(case["request"])
def test_normalize_keeps_declared_order_and_floor():
p = jev.normalize({"b": 0.0, "a": 1.0}, ["a", "b"])
assert list(p) == ["a", "b"]
assert p["b"] > 0 and abs(sum(p.values()) - 1) < 1e-12
def test_normalize_rejects_unknown_option():
with pytest.raises(jev.UnknownOption):
jev.normalize({"a": 0.5, "z": 0.5}, ["a", "b"])
def test_answers_match_response_schema():
choice_q = {"type": "choice", "instructions": "?", "criteria": {"a": "A", "b": "B"}}
score_q = {"type": "score", "instructions": "?", "criteria": ["baixa", "alta"]}
response = {
"model": "m",
"answers": {
"c": jev.choice_answer({"a": 0.2, "b": 0.8}, choice_q),
"n": jev.noul_answer(1.0),
"s": jev.score_answer({"0": 0.25, "1": 0.75}, score_q),
},
"usage": {"input_tokens": 3, "output_tokens": 0},
}
assert jev.response_errors(response) == []
assert response["answers"]["c"]["choice"] == "b"
assert response["answers"]["s"]["score"] == pytest.approx(0.75)
assert response["answers"]["n"]["noul"] < 1
def test_output_tokens_must_be_zero():
response = {"model": "m", "answers": {"n": jev.noul_answer(0.5)},
"usage": {"input_tokens": 1, "output_tokens": 5}}
assert jev.response_errors(response)

35
tests/test_offline.py Normal file
View File

@@ -0,0 +1,35 @@
import os
import socket
import pytest
from dmb.offline import NetworkAccessBlocked, enforce_offline, self_test
def test_external_connection_is_blocked():
enforce_offline()
assert os.environ["HF_HUB_OFFLINE"] == "1"
s = socket.socket()
try:
with pytest.raises(NetworkAccessBlocked):
s.connect(("93.184.216.34", 443))
finally:
s.close()
def test_loopback_is_allowed():
enforce_offline()
server = socket.socket()
server.bind(("127.0.0.1", 0))
server.listen(1)
client = socket.socket()
try:
client.connect(server.getsockname())
finally:
client.close()
server.close()
def test_self_test_reports_guard():
enforce_offline()
assert self_test()["process_guard"].startswith("ok")

85
tests/test_server.py Normal file
View File

@@ -0,0 +1,85 @@
import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from dmb.adapter import Adapter
from dmb.jev import answer_from_distribution, option_descriptions, response_errors
from dmb.server import create_app
FIXTURES = Path(__file__).resolve().parents[1] / "contracts" / "jev" / "fixtures"
CASES = json.loads((FIXTURES / "conformance.json").read_text())["cases"]
class FakeAdapter(Adapter):
"""Escolhe a opção cuja descrição compartilha mais palavras com o estado."""
name = "fake"
def load(self):
pass
def native_context_limit(self):
return 10_000
def count_tokens(self, text):
return len(text.split())
def decide(self, state, questions):
words = set(str(state).lower().split())
answers = {}
for name, q in questions.items():
scores = {k: 1 + len(words & set(d.lower().split())) for k, d in option_descriptions(q).items()}
total = sum(scores.values())
answers[name] = answer_from_distribution(q, {k: v / total for k, v in scores.items()})
return answers, self.count_tokens(str(state))
class BrokenAdapter(FakeAdapter):
def decide(self, state, questions):
answers, n = super().decide(state, questions)
for a in answers.values():
if "probabilities" in a:
a["probabilities"] = dict(reversed(list(a["probabilities"].items())))
return answers, n
@pytest.fixture
def client():
with TestClient(create_app(FakeAdapter("unused", "0" * 40))) as c:
yield c
@pytest.mark.parametrize("case", CASES, ids=[c["name"] for c in CASES])
def test_conformance(client, case):
r = client.post("/v1/systemone", json=case["request"])
if case["expect"] == "ok":
assert r.status_code == 200, r.text
body = r.json()
assert response_errors(body) == []
assert list(body["answers"]) == list(case["request"]["questions"])
for name, q in case["request"]["questions"].items():
if q["type"] == "choice":
assert list(body["answers"][name]["probabilities"]) == list(q["criteria"])
assert body["usage"]["output_tokens"] == 0
assert {"latency_ms", "model_id", "run_id", "adapter_version"} <= body["metadata"].keys()
else:
assert r.status_code == 422
assert r.json()["detail"]["error_type"] == "invalid_request"
def test_malformed_json(client):
r = client.post("/v1/systemone", content=b"{nope", headers={"content-type": "application/json"})
assert r.status_code == 422
def test_reordered_options_are_rejected():
with TestClient(create_app(BrokenAdapter("unused", "0" * 40))) as c:
r = c.post("/v1/systemone", json=CASES[0]["request"])
assert r.status_code == 502
assert r.json()["detail"]["error_type"] == "unknown_option"
def test_health(client):
body = client.get("/health").json()
assert body["status"] == "ready" and body["name"] == "fake"