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

93
dmb/server.py Normal file
View File

@@ -0,0 +1,93 @@
"""Servidor local POST /v1/systemone, idêntico para todos os candidatos (tarefas 46–48, 50)."""
from __future__ import annotations
import json
import os
import time
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from .adapter import Adapter
from .jev import (CONTRACT_VERSION, InvalidRequest, JevError, ModelUnavailable, UnknownOption,
option_keys, response_errors, validate_request)
def build_response(adapter: Adapter, request: dict, answers: dict, input_tokens: int,
latency_ms: float) -> dict:
if list(answers) != list(request["questions"]):
raise UnknownOption(f"perguntas respondidas {list(answers)} != {list(request['questions'])}")
for name, question in request["questions"].items():
answer = answers[name]
if answer.get("type") != question["type"]:
raise UnknownOption(f"{name}: tipo {answer.get('type')} != {question['type']}")
if question["type"] in ("choice", "score"):
keys = option_keys(question)
if list(answer["probabilities"]) != keys:
raise UnknownOption(f"{name}: ordem ou chaves das opções alteradas")
if question["type"] == "choice" and answer["choice"] not in keys:
raise UnknownOption(f"{name}: escolha {answer['choice']!r} fora de criteria")
response = {
"model": adapter.model_id,
"answers": answers,
"usage": {"input_tokens": int(input_tokens), "output_tokens": 0},
"metadata": {
"latency_ms": latency_ms,
"model_id": adapter.model_id,
"run_id": os.environ.get("DMB_RUN_ID", "local"),
"adapter_version": adapter.adapter_version,
"contract_version": CONTRACT_VERSION,
"probabilities_source": adapter.probabilities_source,
},
}
errors = response_errors(response)
if errors:
raise UnknownOption("resposta fora do contrato: " + "; ".join(errors[:3]))
return response
def systemone(adapter: Adapter, request: dict) -> dict:
"""Caminho único usado pelo HTTP e pelo harness em processo."""
validate_request(request)
state = request["state"]
if not isinstance(state, str):
# Todo adaptador recebe texto; estados estruturados viram JSON canônico.
state = json.dumps(state, ensure_ascii=False, sort_keys=True)
started = time.perf_counter()
answers, input_tokens = adapter.decide(state, request["questions"])
latency_ms = (time.perf_counter() - started) * 1000
return build_response(adapter, request, answers, input_tokens, latency_ms)
def create_app(adapter: Adapter) -> FastAPI:
state = {"ready": False}
@asynccontextmanager
async def lifespan(_app):
started = time.perf_counter()
adapter.load()
state.update(ready=True, load_seconds=time.perf_counter() - started)
yield
app = FastAPI(title=f"dmb {adapter.name}", version=adapter.adapter_version, lifespan=lifespan)
@app.get("/health")
def health():
return {"status": "ready" if state["ready"] else "loading", **adapter.describe(),
"load_seconds": state.get("load_seconds")}
@app.post("/v1/systemone")
async def endpoint(http_request: Request):
try:
try:
body = await http_request.json()
except ValueError as error:
raise InvalidRequest(f"JSON inválido: {error}") from error
if not state["ready"]:
raise ModelUnavailable("modelo ainda carregando")
return systemone(adapter, body)
except JevError as error:
return JSONResponse(status_code=error.status, content=error.body())
return app