"""Small fixed-endpoint client for optional OpenRouter Jev relevance judgments.""" from __future__ import annotations import json from dataclasses import dataclass from typing import Any, cast from urllib.error import HTTPError, URLError from urllib.request import HTTPRedirectHandler, Request, build_opener from gewu.domain.contracts import ( FacetName, IngestReviewCreate, JevFacetScore, ProjectRecord, ResearchLens, ) ENDPOINT = "https://openrouter.ai/api/alpha/decisions" MODEL = "typesafe/jev-1.13" MAX_RESPONSE_BYTES = 32_000 @dataclass(frozen=True, slots=True) class JevAssessment: relation_probability: float lens: ResearchLens lens_confidence: float model: str facet_scores: tuple[JevFacetScore, ...] = () class JevUnavailable(RuntimeError): """Jev is not configured or returned an unusable response.""" class _NoRedirect(HTTPRedirectHandler): def redirect_request( self, request: Any, response: Any, code: int, message: str, headers: Any, new_url: str ) -> None: return None class OpenRouterJev: def __init__(self, api_key: str, *, model: str = MODEL) -> None: if not api_key or len(api_key) > 512 or any(char.isspace() for char in api_key): raise ValueError("OpenRouter API key is invalid") if model not in {MODEL, "~typesafe/jev-latest"}: raise ValueError("Unsupported Jev model identifier") self._api_key = api_key self._model = model def assess(self, item: IngestReviewCreate, project: ProjectRecord) -> JevAssessment: facet_candidates: tuple[tuple[FacetName, tuple[str, ...]], ...] = ( ("tags", item.candidate_tags), ("audience", item.candidate_audience), ("use_for", item.candidate_use_for), ("domain", item.candidate_domain), ) facet_questions: dict[str, object] = {} facet_question_map: dict[str, tuple[FacetName, str]] = {} for facet, candidates in facet_candidates: for index, value in enumerate(candidates): key = f"facet_{facet}_{index}" facet_question_map[key] = (facet, value) facet_questions[key] = { "type": "noul", "instructions": ( f"Does the candidate {facet} label accurately describe the passage " "in the context of this project? Judge the label itself, not just " "whether its words appear in the excerpt." ), "criteria": { "true": "This label is a useful, well-supported classification.", "false": "This label is inaccurate, unsupported, or too broad.", }, } state = { "project_title": project.title, "project_north_star": project.north_star, "project_questions": project.key_questions, "candidate_lens": item.lens, "candidate_summary": item.summary, "candidate_relevance_rationale": item.relevance_rationale, "source_excerpt": item.excerpt, "candidate_facets": { facet: candidates for facet, candidates in facet_candidates if candidates }, } criteria = { "technical": "Engineering, implementation, technology, or operational practice.", "conceptual": "Ideas, theory, design concepts, or analytical frameworks.", "cultural-historical": "Cultural, artistic, historical, social, or audience context.", "financial-market": "Costs, revenues, business models, markets, or financing.", } questions: dict[str, object] = { "project_relation": { "type": "noul", "instructions": ( "Does this passage help answer a project question or explain " "the project's technical, conceptual, cultural-historical, or " "financial-market context? Treat indirect context as relevant when " "the connection is plausible." ), "criteria": { "true": "A plausible, specific connection to the project exists.", "false": "The passage has no plausible project connection.", }, }, "best_lens": { "type": "choice", "instructions": "Which project research lens best describes this passage?", "criteria": criteria, }, **facet_questions, } payload = json.dumps( { "model": self._model, "state": state, "questions": questions, }, ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") request = Request( ENDPOINT, data=payload, headers={ "Authorization": f"Bearer {self._api_key}", "Content-Type": "application/json", "Accept": "application/json", }, method="POST", ) try: with build_opener(_NoRedirect()).open(request, timeout=12) as response: if response.status != 200: raise JevUnavailable("Jev request failed") raw = response.read(MAX_RESPONSE_BYTES + 1) except (HTTPError, URLError, TimeoutError, OSError) as error: raise JevUnavailable("Jev request failed") from error if len(raw) > MAX_RESPONSE_BYTES: raise JevUnavailable("Jev response exceeded its size limit") try: result = json.loads(raw) answers = result["answers"] relation = float(answers["project_relation"]["noul"]) lens = answers["best_lens"]["choice"] confidence = float(answers["best_lens"]["confidence"]) served_model = str(result["model"]) facet_scores = tuple( JevFacetScore( facet=facet, value=value, probability=float(answers[key]["noul"]), ) for key, (facet, value) in facet_question_map.items() ) except (KeyError, TypeError, ValueError, json.JSONDecodeError) as error: raise JevUnavailable("Jev response did not match its typed decision schema") from error if ( not 0 <= relation <= 1 or not 0 <= confidence <= 1 or lens not in criteria or len(served_model) > 100 ): raise JevUnavailable("Jev response contained an invalid decision") return JevAssessment( relation, cast(ResearchLens, lens), confidence, served_model, facet_scores )