This commit is contained in:
1 parent
7b2c43ddaf
commit
0e1a2ad008
8 files changed
+191
-27
No files matched your search
@@ -8,7 +8,13 @@ from typing import Any, cast
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import HTTPRedirectHandler, Request, build_opener
|
||||
|
||||
from gewu.domain.contracts import IngestReviewCreate, ProjectRecord, ResearchLens
|
||||
from gewu.domain.contracts import (
|
||||
FacetName,
|
||||
IngestReviewCreate,
|
||||
JevFacetScore,
|
||||
ProjectRecord,
|
||||
ResearchLens,
|
||||
)
|
||||
|
||||
ENDPOINT = "https://openrouter.ai/api/alpha/decisions"
|
||||
MODEL = "typesafe/jev-1.13"
|
||||
@@ -21,6 +27,7 @@ class JevAssessment:
|
||||
lens: ResearchLens
|
||||
lens_confidence: float
|
||||
model: str
|
||||
facet_scores: tuple[JevFacetScore, ...] = ()
|
||||
|
||||
|
||||
class JevUnavailable(RuntimeError):
|
||||
@@ -44,6 +51,30 @@ class OpenRouterJev:
|
||||
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,
|
||||
@@ -52,6 +83,9 @@ class OpenRouterJev:
|
||||
"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.",
|
||||
@@ -59,30 +93,32 @@ class OpenRouterJev:
|
||||
"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": {
|
||||
"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,
|
||||
},
|
||||
},
|
||||
"questions": questions,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
@@ -113,6 +149,14 @@ class OpenRouterJev:
|
||||
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 (
|
||||
@@ -122,4 +166,6 @@ class OpenRouterJev:
|
||||
or len(served_model) > 100
|
||||
):
|
||||
raise JevUnavailable("Jev response contained an invalid decision")
|
||||
return JevAssessment(relation, cast(ResearchLens, lens), confidence, served_model)
|
||||
return JevAssessment(
|
||||
relation, cast(ResearchLens, lens), confidence, served_model, facet_scores
|
||||
)
|
||||
@@ -14,6 +14,7 @@ from gewu.domain.contracts import (
|
||||
IngestReviewCreate,
|
||||
IngestReviewDecision,
|
||||
IngestReviewItem,
|
||||
JevFacetScore,
|
||||
)
|
||||
|
||||
MAX_REVIEW_ITEMS = 100_000
|
||||
@@ -52,6 +53,7 @@ class SQLiteIngestReviewStore:
|
||||
jev_lens TEXT,
|
||||
jev_lens_confidence REAL,
|
||||
jev_model TEXT,
|
||||
jev_facet_scores_json TEXT NOT NULL DEFAULT '[]',
|
||||
status TEXT NOT NULL CHECK(status IN (
|
||||
'needs-user-review', 'included', 'excluded', 'deferred'
|
||||
)),
|
||||
@@ -68,6 +70,7 @@ class SQLiteIngestReviewStore:
|
||||
("jev_lens", "TEXT"),
|
||||
("jev_lens_confidence", "REAL"),
|
||||
("jev_model", "TEXT"),
|
||||
("jev_facet_scores_json", "TEXT NOT NULL DEFAULT '[]'"),
|
||||
):
|
||||
if column not in existing_columns:
|
||||
connection.execute(
|
||||
@@ -118,6 +121,7 @@ class SQLiteIngestReviewStore:
|
||||
jev_lens: str | None = None,
|
||||
jev_lens_confidence: float | None = None,
|
||||
jev_model: str | None = None,
|
||||
jev_facet_scores: tuple[JevFacetScore, ...] = (),
|
||||
) -> IngestReviewItem:
|
||||
payload = item.model_dump(mode="json")
|
||||
digest = _digest({**payload, "submitted_by": submitted_by})
|
||||
@@ -139,9 +143,10 @@ class SQLiteIngestReviewStore:
|
||||
review_id, project_id, run_id, source_revision_id, extraction_id,
|
||||
locator_json, excerpt, lens, summary, relevance_rationale, relevance,
|
||||
status, submitted_by, created_at, payload_digest,
|
||||
jev_relation_probability, jev_lens, jev_lens_confidence, jev_model
|
||||
jev_relation_probability, jev_lens, jev_lens_confidence, jev_model,
|
||||
jev_facet_scores_json
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'needs-user-review', ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'needs-user-review', ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)""",
|
||||
(
|
||||
item.review_id,
|
||||
@@ -162,6 +167,11 @@ class SQLiteIngestReviewStore:
|
||||
jev_lens,
|
||||
jev_lens_confidence,
|
||||
jev_model,
|
||||
json.dumps(
|
||||
[score.model_dump(mode="json") for score in jev_facet_scores],
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
),
|
||||
),
|
||||
)
|
||||
return self._read_item(connection, item.review_id)
|
||||
@@ -274,7 +284,8 @@ class SQLiteIngestReviewStore:
|
||||
"""SELECT review_id, project_id, run_id, source_revision_id, extraction_id,
|
||||
locator_json, excerpt, lens, summary, relevance_rationale, relevance, status,
|
||||
submitted_by, created_at, jev_relation_probability, jev_lens,
|
||||
jev_lens_confidence, jev_model FROM ingest_review_items WHERE review_id=?""",
|
||||
jev_lens_confidence, jev_model, jev_facet_scores_json
|
||||
FROM ingest_review_items WHERE review_id=?""",
|
||||
(review_id,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
@@ -299,6 +310,7 @@ class SQLiteIngestReviewStore:
|
||||
"jev_lens": row[15],
|
||||
"jev_lens_confidence": row[16],
|
||||
"jev_model": row[17],
|
||||
"jev_facet_scores": json.loads(row[18]),
|
||||
},
|
||||
strict=True,
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ ResearchLens = Literal[
|
||||
"cultural-historical",
|
||||
"financial-market",
|
||||
]
|
||||
FacetName = Literal["tags", "audience", "use_for", "domain"]
|
||||
DEFAULT_RESEARCH_LENSES: tuple[ResearchLens, ...] = (
|
||||
"technical",
|
||||
"conceptual",
|
||||
@@ -115,6 +116,12 @@ class ProjectContextResponse(StrictRecord):
|
||||
lens: ProjectLens
|
||||
|
||||
|
||||
class JevFacetScore(StrictRecord):
|
||||
facet: FacetName
|
||||
value: SearchFacetValue
|
||||
probability: FiniteFloat = Field(ge=0, le=1)
|
||||
|
||||
|
||||
class IngestReviewItem(StrictRecord):
|
||||
review_id: RecordId
|
||||
project_id: RecordId
|
||||
@@ -127,6 +134,7 @@ class IngestReviewItem(StrictRecord):
|
||||
summary: str = Field(min_length=1, max_length=2000)
|
||||
relevance_rationale: str = Field(min_length=1, max_length=2000)
|
||||
relevance: Literal["borderline", "low"] = "borderline"
|
||||
jev_facet_scores: tuple[JevFacetScore, ...] = Field(default=(), max_length=20)
|
||||
jev_relation_probability: FiniteFloat | None = Field(default=None, ge=0, le=1)
|
||||
jev_lens: ResearchLens | None = None
|
||||
jev_lens_confidence: FiniteFloat | None = Field(default=None, ge=0, le=1)
|
||||
@@ -156,6 +164,29 @@ class IngestReviewCreate(StrictRecord):
|
||||
summary: str = Field(min_length=1, max_length=2000)
|
||||
relevance_rationale: str = Field(min_length=1, max_length=2000)
|
||||
relevance: Literal["borderline", "low"] = "borderline"
|
||||
candidate_tags: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
|
||||
candidate_audience: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
|
||||
candidate_use_for: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
|
||||
candidate_domain: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def limit_total_facet_candidates(self) -> IngestReviewCreate:
|
||||
values = (
|
||||
*self.candidate_tags,
|
||||
*self.candidate_audience,
|
||||
*self.candidate_use_for,
|
||||
*self.candidate_domain,
|
||||
)
|
||||
if len(values) > 20:
|
||||
raise ValueError("At most 20 facet candidates may be assessed")
|
||||
if len({(facet, value.casefold()) for facet, candidates in (
|
||||
("tags", self.candidate_tags),
|
||||
("audience", self.candidate_audience),
|
||||
("use_for", self.candidate_use_for),
|
||||
("domain", self.candidate_domain),
|
||||
) for value in candidates}) != len(values):
|
||||
raise ValueError("Facet candidates must be unique within each facet")
|
||||
return self
|
||||
|
||||
|
||||
class IngestReviewDecision(StrictRecord):
|
||||
|
||||
@@ -452,6 +452,7 @@ def create_mcp_server(
|
||||
assessment.lens_confidence if assessment is not None else None
|
||||
),
|
||||
jev_model=assessment.model if assessment is not None else None,
|
||||
jev_facet_scores=(assessment.facet_scores if assessment is not None else ()),
|
||||
)
|
||||
return json.dumps(record.model_dump(mode="json"), ensure_ascii=False)
|
||||
|
||||
|
||||
Reference in new issue
Block a user