Skip to content

Commit 9d777da

Browse files
committed
updated typechecks
1 parent 74e4cc0 commit 9d777da

15 files changed

Lines changed: 99 additions & 55 deletions

File tree

backend/app/core/storage_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import json
99
import logging
1010
import mimetypes
11+
from collections.abc import Mapping, Sequence
1112
from datetime import datetime
1213
from io import BytesIO
1314
from pathlib import Path
14-
from typing import Literal
15+
from typing import Any, Literal
1516
from urllib.parse import unquote, urlparse
1617
from uuid import UUID
1718

@@ -109,7 +110,7 @@ def upload_to_object_store(
109110

110111
def upload_jsonl_to_object_store(
111112
storage: CloudStorage,
112-
results: list[dict],
113+
results: Sequence[Mapping[str, Any]],
113114
filename: str,
114115
subdirectory: str,
115116
format: Literal["json", "jsonl"] = "jsonl",

backend/app/crud/evaluations/core.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import logging
2-
from typing import Any
2+
from collections.abc import Mapping, Sequence
3+
from typing import Any, cast
34
from uuid import UUID
45

56
from langfuse import Langfuse
6-
from sqlmodel import Session, select
7+
from sqlmodel import Session, col, select
78

89
from app.core.cloud.storage import get_cloud_storage
910
from app.core.db import engine
@@ -119,7 +120,7 @@ def list_evaluation_runs(
119120
project_id: int,
120121
limit: int = 50,
121122
offset: int = 0,
122-
) -> list[EvaluationRun]:
123+
) -> Sequence[EvaluationRun]:
123124
"""
124125
List all evaluation runs for an organization and project.
125126
@@ -138,7 +139,7 @@ def list_evaluation_runs(
138139
.where(EvaluationRun.organization_id == organization_id)
139140
.where(EvaluationRun.project_id == project_id)
140141
.where(EvaluationRun.type == EvaluationType.TEXT.value)
141-
.order_by(EvaluationRun.inserted_at.desc())
142+
.order_by(col(EvaluationRun.inserted_at).desc())
142143
.limit(limit)
143144
.offset(offset)
144145
)
@@ -301,7 +302,7 @@ def get_or_fetch_score(
301302
logger.info(
302303
f"[get_or_fetch_score] Returning existing score | evaluation_id={eval_run.id}"
303304
)
304-
return eval_run.score
305+
return cast(EvaluationScore, eval_run.score)
305306

306307
logger.info(
307308
f"[get_or_fetch_score] Fetching score from Langfuse | "
@@ -339,7 +340,7 @@ def get_or_fetch_score(
339340
update_evaluation_run(
340341
session=session,
341342
eval_run=eval_run,
342-
update=EvaluationRunUpdate(score=score),
343+
update=EvaluationRunUpdate(score=cast(dict[str, Any], score)),
343344
)
344345

345346
total_traces = len(score.get("traces", []))
@@ -356,7 +357,7 @@ def _upload_score_traces(
356357
session: Session,
357358
eval_run_id: int,
358359
project_id: int,
359-
traces: list[dict[str, Any]],
360+
traces: Sequence[Mapping[str, Any]],
360361
) -> str | None:
361362
"""Upload per-trace records to S3 for an evaluation run.
362363
@@ -399,7 +400,7 @@ def persist_score_traces(
399400
eval_run_id: int,
400401
organization_id: int,
401402
project_id: int,
402-
traces: list[dict[str, Any]],
403+
traces: Sequence[Mapping[str, Any]],
403404
) -> EvaluationRun | None:
404405
"""Persist the Q&A trace skeleton to S3 and record the ``score_trace_url``
405406
pointer, WITHOUT touching the ``score`` column (keeps the run score-less
@@ -484,12 +485,13 @@ def save_score(
484485
# IF TRACES DATA IS STORED IN S3 URL THEN HERE WE ARE JUST STORING THE SUMMARY SCORE
485486
# TODO: Evaluate whether this behaviour is needed or completely discard the storing data in db
486487
if score_trace_url:
487-
db_score = {"summary_scores": summary_score}
488-
if score.get("overall") is not None:
489-
db_score["overall"] = score["overall"]
488+
db_score: dict[str, Any] = {"summary_scores": summary_score}
489+
overall = score.get("overall")
490+
if overall is not None:
491+
db_score["overall"] = overall
490492
else:
491493
# fallback to store data in db if failed to store in s3
492-
db_score = score
494+
db_score = cast(dict[str, Any], score)
493495

494496
update_evaluation_run(
495497
session=session,
@@ -539,6 +541,8 @@ def group_traces_by_question_id(
539541

540542
for trace in traces:
541543
question_id = trace.get("question_id")
544+
if question_id is None:
545+
continue
542546
if question_id not in groups:
543547
groups[question_id] = []
544548
groups[question_id].append(trace)

backend/app/crud/evaluations/cron_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Any
1212

1313
from sqlalchemy import Integer
14-
from sqlmodel import Session, select
14+
from sqlmodel import Session, col, select
1515

1616
from app.core.batch import (
1717
BatchJobState,
@@ -49,7 +49,7 @@ def fetch_processing_runs(
4949
statement = select(EvaluationRun).where(
5050
EvaluationRun.type == eval_type,
5151
EvaluationRun.status == "processing",
52-
EvaluationRun.batch_job_id.is_not(None),
52+
col(EvaluationRun.batch_job_id).is_not(None),
5353
)
5454
return list(session.exec(statement).all())
5555

backend/app/crud/evaluations/dataset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from fastapi import HTTPException
1515
from sqlalchemy import Integer, cast
1616
from sqlalchemy.exc import IntegrityError
17-
from sqlmodel import Session, select
17+
from sqlmodel import Session, col, select
1818

1919
from app.core.cloud.storage import CloudStorage
2020
from app.core.config import settings
@@ -228,7 +228,7 @@ def list_datasets(
228228
)
229229

230230
statement = (
231-
statement.order_by(EvaluationDataset.inserted_at.desc())
231+
statement.order_by(col(EvaluationDataset.inserted_at).desc())
232232
.limit(limit)
233233
.offset(offset)
234234
)

backend/app/crud/evaluations/fast.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import logging
2121
from concurrent.futures import ThreadPoolExecutor, as_completed
22-
from typing import Any
22+
from typing import Any, cast
2323

2424
import numpy as np
2525
import openai
@@ -80,12 +80,14 @@
8080
from app.crud.evaluations.score import (
8181
COSINE_SCORE_COMMENT,
8282
COSINE_SCORE_NAME,
83+
DEFAULT_CATEGORY,
8384
JUDGE_FAILED_REASON,
8485
UNSCOREABLE_EMBEDDING_FAILED,
8586
UNSCOREABLE_EMPTY_GROUND_TRUTH,
8687
UNSCOREABLE_EMPTY_OUTPUT,
8788
EvaluationScore,
8889
OverallSummary,
90+
SummaryScore,
8991
TraceData,
9092
TraceScore,
9193
compute_overall_summary,
@@ -599,11 +601,13 @@ def _merge_response_chunks(
599601

600602
results: list[dict[str, Any]] = []
601603
for chunk_index in sorted(chunk_job_by_index):
604+
raw_output_url = chunk_job_by_index[chunk_index].raw_output_url
605+
assert raw_output_url is not None # guaranteed by the filter above
602606
results.extend(
603607
_load_unit_from_s3(
604608
session=session,
605609
project_id=eval_run.project_id,
606-
url=chunk_job_by_index[chunk_index].raw_output_url,
610+
url=raw_output_url,
607611
)
608612
)
609613

@@ -881,7 +885,7 @@ def _attach_metric_scores(
881885
*,
882886
spec: JudgeMetricSpec,
883887
judge_results: dict[str, JudgeResult],
884-
summary_scores: list[dict[str, Any]],
888+
summary_scores: list[SummaryScore],
885889
) -> None:
886890
"""Append one metric's run-level summary score from the combined results.
887891
@@ -959,7 +963,7 @@ def _stage3_score_and_trace(
959963
similarities: list[float] = []
960964
unscoreable: dict[str, str] = {} # {ref: reason}
961965
write_items: list[dict[str, Any]] = []
962-
summary_scores: list[dict[str, Any]] = []
966+
summary_scores: list[SummaryScore] = []
963967
overall: OverallSummary | None = None
964968

965969
if is_judge_run:
@@ -993,6 +997,7 @@ def _stage3_score_and_trace(
993997
else:
994998
unscoreable[ref] = UNSCOREABLE_EMBEDDING_FAILED
995999
continue
1000+
assert embedding_pair is not None # guaranteed by has_embeddings above
9961001
cosine = calculate_cosine_similarity(
9971002
embedding_pair["output_embedding"],
9981003
embedding_pair["ground_truth_embedding"],
@@ -1171,7 +1176,7 @@ def _stage3_score_and_trace(
11711176
traces: list[TraceData] = []
11721177
for response in response_results:
11731178
item_id = response["item_id"]
1174-
ref = item_id_to_ref.get(item_id, item_id)
1179+
ref = item_id_to_ref[item_id] if item_id in item_id_to_ref else item_id
11751180
trace_scores: list[TraceScore] = []
11761181
# v2 carries no cosine score or placeholder — only the judge scores below.
11771182
if not is_judge_run:
@@ -1253,6 +1258,7 @@ def _stage3_score_and_trace(
12531258
"llm_answer": response.get("generated_output", ""),
12541259
"ground_truth_answer": response.get("ground_truth", ""),
12551260
"question_id": response.get("question_id"),
1261+
"category": response.get("category") or DEFAULT_CATEGORY,
12561262
"scores": trace_scores,
12571263
}
12581264
)
@@ -1409,7 +1415,7 @@ def run_fast_evaluation(
14091415
)
14101416
if saved is not None:
14111417
eval_run = saved
1412-
eval_run.score = score
1418+
eval_run.score = cast(dict[str, Any], score)
14131419

14141420
logger.info(
14151421
f"[run_fast_evaluation] {log_prefix} Fast evaluation completed | "

backend/app/crud/evaluations/langfuse.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import Any
1414

1515
from langfuse import Langfuse
16+
from langfuse.api.commons.types.score_v1 import ScoreV1_Text
1617

1718
from app.core.langfuse.langfuse import format_langfuse_error, set_trace_attributes
1819
from app.crud.evaluations.merge import compute_summary_scores
@@ -280,6 +281,12 @@ def update_traces_with_cosine_scores(
280281
comment = f"Cannot compute: {reason}"
281282
else:
282283
value = score_item.get("cosine_similarity")
284+
if value is None:
285+
logger.warning(
286+
"[update_traces_with_cosine_scores] "
287+
f"Score item missing cosine_similarity, skipping | trace_id={trace_id}"
288+
)
289+
continue
283290
comment = COSINE_SCORE_COMMENT
284291

285292
try:
@@ -517,7 +524,7 @@ def _fetch_single_trace(trace_id: str) -> TraceData | None:
517524
"question": "",
518525
"llm_answer": "",
519526
"ground_truth_answer": "",
520-
"question_id": "",
527+
"question_id": None,
521528
"scores": [],
522529
}
523530

@@ -552,7 +559,11 @@ def _fetch_single_trace(trace_id: str) -> TraceData | None:
552559
if trace.scores:
553560
for score in trace.scores:
554561
score_name = score.name
555-
score_value = score.value
562+
score_value = (
563+
score.string_value
564+
if isinstance(score, ScoreV1_Text)
565+
else score.value
566+
)
556567
score_comment = score.comment
557568
# Get data_type from Langfuse score, default to NUMERIC
558569
data_type = getattr(score, "data_type", None) or "NUMERIC"

backend/app/crud/evaluations/merge.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import itertools
1010
import logging
1111
from collections import Counter
12-
from typing import Any
12+
from typing import cast
1313

1414
import numpy as np
1515

@@ -18,6 +18,7 @@
1818
COSINE_SCORE_NAME,
1919
DEFAULT_CATEGORY,
2020
EvaluationScore,
21+
NumericSummaryScore,
2122
SummaryScore,
2223
TraceData,
2324
TraceScore,
@@ -126,9 +127,11 @@ def apply_cosine_breakdown(
126127
breakdown = summarize_unscoreable(unscoreable)
127128
for entry in summary_scores:
128129
if entry.get("name") == COSINE_SCORE_NAME:
130+
# Cosine is always a NUMERIC score; only that shape carries these fields.
131+
numeric_entry = cast(NumericSummaryScore, entry)
129132
if total_items is not None:
130-
entry["total_items"] = total_items
131-
entry["unscoreable"] = breakdown
133+
numeric_entry["total_items"] = total_items
134+
numeric_entry["unscoreable"] = breakdown
132135
return summary_scores
133136

134137

@@ -177,7 +180,7 @@ def _merge_single_trace(existing: TraceData, fresh: TraceData) -> TraceData:
177180
for fresh_score in fresh.get("scores", []):
178181
merged_scores_by_name[fresh_score["name"]] = fresh_score
179182

180-
merged: dict[str, Any] = {
183+
merged: TraceData = {
181184
"trace_id": fresh.get("trace_id") or existing.get("trace_id", ""),
182185
"question": fresh.get("question") or existing.get("question", ""),
183186
"llm_answer": fresh.get("llm_answer") or existing.get("llm_answer", ""),
@@ -205,6 +208,7 @@ def _reconcile_trace(
205208
or ``updated``. Exactly one of ``existing``/``fresh`` may be None, never both.
206209
"""
207210
if existing is None:
211+
assert fresh is not None, "exactly one of existing/fresh must be set"
208212
return fresh, "added"
209213
if fresh is None:
210214
return existing, "reused"

0 commit comments

Comments
 (0)