Skip to content

refactor(evaluation): split modules - #1202

Merged
AkhileshNegi merged 25 commits into
mainfrom
refactor/evals2.0
Sep 21, 2026
Merged

AkhileshNegi merged 25 commits into
mainfrom
refactor/evals2.0

Conversation

@AkhileshNegi

@AkhileshNegi AkhileshNegi commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Issue

No linked issue. Reliability fixes came out of an audit of the v2 fast-eval / iteration path while splitting it.

Summary

Started as a structural split of crud/evaluations/fast.py; grew to include three reliability fixes on the same code that the split made visible. Existing behaviour is otherwise unchanged.

1. Module split (no behaviour change)

fast.py had reached 1429 lines around a 375-line _stage3_score_and_trace mixing v1 cosine and v2 judge scoring, trace building, cost attachment and the run rollup in one body. Extracted, component-wise:

Module Carries
crud/evaluations/fast_results.py pure per-item S3 unit shapes, usage sums, failure threshold
crud/evaluations/fast_chunks.py batch_job bookkeeping + chunk queries/cleanup
crud/evaluations/fast_cosine.py pure v1 cosine scoring
crud/evaluations/fast_traces.py pure TraceData building
crud/evaluations/judge_stage.py run-level v2 judge orchestration + per-metric rollup
crud/evaluations/judge_prompts.py rubric text, moved out of score.py
crud/evaluations/retry.py OpenAI retry policy, previously duplicated in fast + judge
services/evaluations/iteration_state.py graph state + pure round/stop arithmetic
services/evaluations/iteration_checkpointer.py psycopg pool + PostgresSaver

LOC: fast.py 1429 → 1006, score.py 359 → 218, iteration_graph.py 612 → 504. Largest function 375 → 115 lines. fast.py stays above a 400-line target because stage 1/2 IO is pinned there by existing tests patching app.crud.evaluations.fast.*.

Two small deltas that narrow existing risk: _effective_duplication_factor skips the dataset query when the run already carries a factor, and _cleanup_response_chunks now guards storage resolution as well as the deletes.

2. Score unit persisted before the completed transition

run_fast_evaluation marked a run completed and only then called save_score, with chunk cleanup and the Langfuse sync in between. A crash in that window (OOM, SIGKILL, hard timeout) left the run completed with its score unit never written, and nothing recovers it: the aggregate short-circuits on completed and the cron barrier only selects processing. On v2 that unit is the only copy of the per-row judge scores and reasoning; no Langfuse traces exist to re-fetch from.

Now save_score runs first, the completed transition second, and cleanup + Langfuse sync last as a best-effort tail that can never fail the run. A crash leaves the run processing and visibly unfinished instead of silently scoreless. Follow-ons: the completed transition no longer rewrites score (already persisted), and save_score returning None raises instead of completing a scoreless run.

3. Guarded iteration-loop resumes

dispatch_pending_evaluation_iteration_resumes fanned a resume=True graph step out to every PROCESSING loop every tick, unconditionally. A step slower than the tick got a second one dispatched on top of it: two workers against the same LangGraph checkpoint thread, and a duplicate eval run or improvement job charged for. A loop whose sub-job wedged collected resumes forever.

  • In-flight cooldown. New nullable evaluation_iteration_run.last_dispatched_at (migration 084, no backfill; NULL means never dispatched). Stamped at kickoff and after every cron dispatch. Rows stamped inside EVAL_ITERATION_DISPATCH_COOLDOWN_MINUTES are skipped. Timestamp heuristic, not a lock; ceiling and upgrade path noted in a ponytail: comment.
  • Zombie reaper. Rows still PROCESSING past EVAL_ITERATION_STALL_THRESHOLD_HOURS since kickoff are failed via mark_iteration_run_failed (now public), failure callback included. Threshold deliberately generous since a false positive kills a live loop and POSTs a failure webhook.
  • Single terminal writer. finalize_node now no-ops when the row is no longer PROCESSING. Before, an in-flight step finishing after a reap overwrote FAILED with COMPLETED and sent a second, contradictory callback. First terminal writer wins, exactly one callback goes out.

docs/wiki/modules/evaluations.md updated for the module split, the score-persist ordering, and both cron guards.

Checklist

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Tests. New files: test_fast_results.py, test_fast_cosine.py, test_fast_traces.py, test_fast_chunk_cleanup.py, test_judge_stage.py, test_iteration_state.py (extracted pure functions), test_fast_score_durability.py (reverting the reorder fails all 5), test_cron_iteration_guards.py (cooldown + reaper). Pre-existing tests touched only to age the kickoff stamp past the cooldown and to add the already-terminal finalize_node case. Mutation-checked: removing the cron guards fails their tests.

Migration. 084_add_last_dispatched_at_to_evaluation_iteration_run chains after main's 083_add_llm_call_metadata (single alembic head).

Not in scope, from the same audit: no failure threshold on the judge stage (R1), no healer for an eval run wedged in processing post-merge (R3), and the v2 read path still routes judged runs through Langfuse.

🤖 Generated with Claude Code

AkhileshNegi and others added 2 commits September 8, 2026 02:29
Structural refactor. Existing tests pass unchanged (3310 passed, 6 skipped)
and pyright reports the same 8 pre-existing errors as main.

Motivation: `crud/evaluations/fast.py` had grown to 1429 lines around a
375-line `_stage3_score_and_trace` mixing v1 cosine and v2 judge scoring,
trace building, cost attachment and the run rollup in one body.

Extracted, component-wise:
  crud/evaluations/
    fast_results.py  pure per-item S3 unit shapes, usage sums, threshold
    fast_chunks.py   batch_job bookkeeping + chunk queries/cleanup
    fast_cosine.py   pure v1 cosine scoring
    fast_traces.py   pure TraceData building
    judge_stage.py   run-level v2 judge orchestration + metric rollup
    judge_prompts.py rubric text, moved out of score.py
    retry.py         OpenAI retry policy, was duplicated in fast + judge
  services/evaluations/
    iteration_state.py        graph state + pure round/stop arithmetic
    iteration_checkpointer.py psycopg pool + PostgresSaver

`score.py` no longer carries judge rubric text, so `judge.py` imports its
prompts directly instead of back through the score module.

LOC: fast.py 1429->1006, score.py 359->218, iteration_graph.py 612->504.
Largest function 375->115 lines.

Two deliberate behaviour deltas, both narrowing existing risk:
  - `_effective_duplication_factor` skips the `get_dataset_by_id` query when
    the run already carries a factor. Same value, one fewer query.
  - `_cleanup_response_chunks` now guards storage resolution as well as the
    deletes. It runs between the completed transition and `save_score`, so a
    raise there would flip a completed run to failed *and* lose its score
    unit; main only guarded the delete loop.

fast.py stays above a 400-line target: stage 1/2 IO is pinned there by
existing tests patching `app.crud.evaluations.fast.*`, and moving it would
mean editing those tests.

New tests cover the extracted pure functions and the cleanup guard only
(71 cases); no existing test file was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two reliability fixes on the v2 (is_judge_run) eval path. Behaviour changes on
purpose; no existing test edited.

R2 — score unit persisted before the completed transition
---------------------------------------------------------
`run_fast_evaluation` marked a run `completed` and only then called
`save_score`, with chunk cleanup and the Langfuse sync in between. A crash in
that window (OOM, SIGKILL, hard timeout) left the run reading `completed` with
its score unit never written, and nothing recovers it: the aggregate
short-circuits on `status == "completed"` and the cron barrier only selects
`processing`. On v2 that unit is the *only* copy of the per-row judge scores and
reasoning — no Langfuse traces are created for a judged run, so unlike v1 there
is no second copy to re-fetch and merge from.

`save_score` now runs first (stage 4), the completed transition second (stage 5),
and cleanup + Langfuse sync last as a best-effort tail. A failure now leaves the
run `processing` and visibly unfinished instead of silently scoreless. Note this
does not make it retryable — a `failed` run is not re-enqueued either — the win
is that the loss is visible rather than silent.

Two follow-on changes:
  * the completed transition no longer rewrites `score`; save_score already
    persisted it, and the duplicate write differed for v1 (it carried
    `overall: None`). Final DB state is unchanged from before.
  * `save_score` returning None (run row gone) now raises instead of completing
    the run without a score.

`save_score` opens a second Session on the same row, so stage 3's write must stay
committed before it — noted in a comment, since nothing else enforces it.

R4 — in-flight and zombie guards on the iteration resume tick
-------------------------------------------------------------
`dispatch_pending_evaluation_iteration_resumes` fanned a `resume=True` graph step
out to every PROCESSING loop every tick, unconditionally. A step slower than the
5-minute tick got a second one dispatched on top of it — two workers against the
same LangGraph checkpoint thread, and a duplicate eval run or improvement job
charged for. A row whose sub-job wedged collected resumes forever, with no max age.

Adds `evaluation_iteration_run.last_dispatched_at` (migration 083, nullable, no
backfill — NULL means never dispatched, which is what a fresh loop needs) and two
guards:
  * dispatch inside EVAL_ITERATION_DISPATCH_COOLDOWN_MINUTES (10, floored on
    CELERY_TASK_TIME_LIMIT plus queue wait) → skip,
  * still processing past EVAL_ITERATION_STALL_THRESHOLD_HOURS (72) since kickoff
    → reap via `mark_iteration_run_failed`, failure callback included.

The cooldown is a timestamp heuristic, not a lock; the ceiling and its upgrade
path are named in a `ponytail:` comment. The reap threshold is deliberately
generous because a false positive kills a live loop and POSTs a failure webhook —
the genuinely unbounded case it exists for is a loop interrupted on an eval run
wedged in `processing` (audit finding R3, not fixed here); a crashed graph step
already fails itself, and a SIGKILLed one resumes from its checkpoint.

`_mark_iteration_run_failed` is now public (`mark_iteration_run_failed`) so the
reaper can call it. The summary dict keeps exactly its two keys — skip and reap
counts go to the log, since `test_cron_iteration.py` asserts it by equality.

Verification
------------
  * 3322 passed, 6 skipped; zero existing test files edited (the one touched
    test file is the docstring of a test added in the previous commit, whose
    stated rationale referenced the old ordering)
  * new tests mutation-checked: reverting the reorder fails 5/5, removing the
    cron guards fails 2/7
  * pyright: 10 errors on the changed set, all pre-existing
  * ruff + pre-commit clean; `docs/wiki/modules/evaluations.md` updated

Not in scope: R1 (no failure threshold on the judge stage), R3 (no healer for a
run stuck post-merge), and the v2 read path routing judged runs through Langfuse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ready-for-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: ProjectTech4DevAI/kaapi-backend/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 13b95531-ece3-471d-af7a-b2cecf429c34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Resolve docs/wiki/modules/evaluations.md by keeping both sides: this
branch's crud/evaluations file split and score-persist ordering, main's
plain-text ai_summary rationale and prompt-improvement stop_reason guard.
Renumber 083_add_last_dispatched_at_to_evaluation_iteration_run to 084 so
it chains after main's 083_add_llm_call_metadata (single alembic head).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

OpenAPI changes   🟢 1 non-breaking change

Tip

Safe to merge from an API-contract perspective.

Full changelog  ·  1
Method Path Change
🟢 POST /api/v2/evaluations/iterations api operation id Evaluation v2-create_evaluation_iteration_v2 removed and replaced with Evaluation v2-create_evaluation_iteration

main00efcf97 · generated by oasdiff

AkhileshNegi and others added 4 commits September 14, 2026 16:26
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

@Ayush8923 Ayush8923 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will check overall in deep later but added the few high level comments. also, please remove the unwanted python comments added the one line and 2 liner comment. Also, remove the logger info and warning only add if something breakage point.

Comment on lines +584 to +585
response_results: list[dict[str, Any]],
embedding_results: list[dict[str, Any]] | None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we know the response result structure, then remove the Any type and use the proper type safety check.

Comment thread backend/app/crud/evaluations/fast.py Outdated
Comment on lines +621 to +622
response_results: list[dict[str, Any]],
embedding_results: list[dict[str, Any]] | None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too, just check everywhere where you have added the Any.

Comment on lines 148 to 153
logger.info(
f"[wait_eval_node] Round scored | iteration_run_id={state['iteration_run_id']} | "
f"round_number={state['round_number']} | stop_score={stop_score} | "
f"consecutive_low_delta_rounds={consecutive_low_delta_rounds} | "
f"consecutive_low_delta_rounds={update['consecutive_low_delta_rounds']} | "
f"stop_reason={update.get('stop_reason')}"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these logger info and warning not needed everywhere, add these only if we know this is breakage point.

AkhileshNegi and others added 2 commits September 15, 2026 18:36
The cron zombie reaper (mark_iteration_run_failed) and the graph's own
finalize_node both write the iteration run's terminal status. The reaper
already checks status == PROCESSING before writing; finalize_node did not,
so an in-flight step finishing after a reap overwrote FAILED with COMPLETED
and sent a second, contradictory callback.

finalize_node now no-ops when the row is no longer PROCESSING. First
terminal writer wins, exactly one callback goes out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AkhileshNegi AkhileshNegi changed the title refactor(evaluation): split fast-eval v2 into focused modules refactor(evaluation): split fast-eval v2 modules, harden score persist + iteration resumes Sep 15, 2026
Trim multiline comments to single-line essential rationale for EVAL_ITERATION_DISPATCH_COOLDOWN_MINUTES and EVAL_ITERATION_STALL_THRESHOLD_HOURS.
@AkhileshNegi
AkhileshNegi marked this pull request as ready for review September 15, 2026 13:30
@AkhileshNegi AkhileshNegi changed the title refactor(evaluation): split fast-eval v2 modules, harden score persist + iteration resumes refactor(evaluation): split modules Sep 15, 2026
@AkhileshNegi AkhileshNegi self-assigned this Sep 15, 2026

@Ayush8923 Ayush8923 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to go. but added few comments for cleanups perspective.

Comment thread backend/app/crud/evaluations/cron.py Outdated
from app.services.evaluations.iteration_graph import mark_iteration_run_failed

runs = list_processing_evaluation_iteration_runs(session=session)
now_ = now()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we simply write now instead of the now_?

Comment thread backend/app/crud/evaluations/fast.py Outdated
Comment on lines +147 to +152
def _log_prefix(eval_run: EvaluationRun) -> str:
return (
f"[org={eval_run.organization_id}]"
f"[project={eval_run.project_id}]"
f"[eval={eval_run.id}]"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good, if we actually need logs then create the one helper function and use that function everywhere if needed instead of importing the logger in every file.

Comment thread backend/app/crud/evaluations/fast.py Outdated
delete_batch_job,
get_batch_job,
)
from app.crud.job import get_batch_job

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not used anywhere. please check.

Comment thread backend/app/crud/evaluations/fast.py Outdated
session: Session,
eval_run: EvaluationRun,
job_type: str,
config: dict[str, Any],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's try to avoid the any type.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the try safety checks issue we have remove all the any type and add the proper type safety checks.

Comment on lines +61 to +153
def _create_stage_job(
*,
session: Session,
eval_run: EvaluationRun,
job_type: str,
config: dict[str, Any],
raw_output_url: str | None,
total_items: int,
) -> BatchJob:
return create_batch_job(
session=session,
batch_job_create=BatchJobCreate(
provider="openai",
job_type=job_type,
config={"run_mode": RunModeEnum.FAST.value, **config},
raw_output_url=raw_output_url,
total_items=total_items,
organization_id=eval_run.organization_id,
project_id=eval_run.project_id,
),
)


def create_response_chunk_job(
*,
session: Session,
eval_run: EvaluationRun,
chunk_index: int,
model: str | None,
results: list[dict[str, Any]],
raw_output_url: str | None,
) -> BatchJob:
"""Mark one response chunk done."""
return _create_stage_job(
session=session,
eval_run=eval_run,
job_type=JOB_TYPE_EVALUATION_FAST_CHUNK,
config={
"endpoint": _RESPONSES_ENDPOINT,
"model": model,
"usage": sum_usage(results, RESPONSE_USAGE_KEYS),
CHUNK_CONFIG_RUN_ID: eval_run.id,
CHUNK_CONFIG_INDEX: chunk_index,
},
raw_output_url=raw_output_url,
total_items=len(results),
)


def create_merged_response_job(
*,
session: Session,
eval_run: EvaluationRun,
model: str | None,
results: list[dict[str, Any]],
raw_output_url: str | None,
) -> BatchJob:
"""Mark the merged responses unit done; its id is the aggregate's retry guard."""
return _create_stage_job(
session=session,
eval_run=eval_run,
job_type=JOB_TYPE_EVALUATION_FAST,
config={
"endpoint": _RESPONSES_ENDPOINT,
"model": model,
"usage": sum_usage(results, RESPONSE_USAGE_KEYS),
},
raw_output_url=raw_output_url,
total_items=len(results),
)


def create_embedding_job(
*,
session: Session,
eval_run: EvaluationRun,
embedding_model: str,
results: list[dict[str, Any]],
raw_output_url: str | None,
) -> BatchJob:
"""Mark the embeddings unit done."""
return _create_stage_job(
session=session,
eval_run=eval_run,
job_type=JOB_TYPE_EMBEDDING_FAST,
config={
"endpoint": _EMBEDDINGS_ENDPOINT,
"embedding_model": embedding_model,
"usage": sum_usage(results, EMBEDDING_USAGE_KEYS),
},
raw_output_url=raw_output_url,
total_items=len(results),
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what i have seen, these functions are basically wrapping another function inside them. We could directly use/import the child function wherever it’s needed instead of creating a separate function that simply returns another function.

this would also reduce the amount of code in this file and make it a bit cleaner.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and make this _create_stage_job publically accessible instead of the private function.

Comment on lines +170 to +173
logger.info(
f"[delete_response_chunk_artifacts] Removed {len(chunk_jobs)} chunk "
f"artifacts | eval_run_id={eval_run_id}"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed.

Comment on lines +34 to +37

Stamped `last_dispatched_at` up front: kickoff enqueues the first graph step
right after this returns, and the cron's cooldown has to cover that step like
any other, or the first tick races it on the same checkpoint thread.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this content needed?

Comment on lines +275 to +283
if iteration_run.status != EvaluationIterationStatusEnum.PROCESSING:
# Reaper (or another terminal writer) got here first; its callback
# already went out, so a second one would contradict it.
logger.warning(
f"[finalize_node] Row already terminal, skipping | "
f"iteration_run_id={state['iteration_run_id']} | "
f"status={iteration_run.status.value}"
)
return {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this actually be useful? we mostly check the data directly from the DB, and it’s very rare that we would check this warning in the logs.

If this is going to remain mostly redundant code, I think we can remove it. As far as I understand, we’re already checking the status in the DB, and here we’re just returning a null object.

If you want, I can  make it sound a bit more concise and natural for a GitHub PR comment.

AkhileshNegi and others added 9 commits September 21, 2026 09:40
- expose create_stage_job and drop the three thin wrappers around it
- share build_log_prefix from core instead of repeating the f-string
- trim progress-only logs in the iteration graph and chunk cleanup
- shorten comments/docstrings, rename now_ to current_time

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Drop the _v2 filename suffix in favour of routes/evaluations/v2/ and
docs/evaluation/v2/ so v1 and v2 files mirror each other by name.
Aggregate the four v2 routers in v2/__init__.py so main.py mounts one
router instead of four.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ived total_items

- Merge v2 markers (is_judge_run, callback_url, duplication_factor) and status/total_items into the initial create_evaluation_run INSERT instead of follow-up UPDATEs
- Compute total_items directly from dataset_metadata without fetching items: original_items_count * effective_duplication_factor
- Drops item-list fetch from trigger path (3 DB writes + S3/Langfuse call down to 1 INSERT on happy path)
- Widened create_evaluation_run and create_evaluation_run_or_409 to accept optional v2-only params with safe defaults; v1 batch path unchanged
- Updated tests: fanout partitioning test now uses metadata-sized dataset; dispatch-failure test replaces removed item-fetch error scenario

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Rename migration to avoid conflict with 084_assessment_submissions_and_result_files.
Update revision chain: 084 revises 083, 085 revises 084.
Logger cleanup:
- Remove 18 routine operation loggers (stages skipped/running/finished)
- Keep warnings/errors for actual failure conditions
- Affected: fast.py, cron.py, judge_stage.py, iteration.py

Code simplification:
- Delete create_stage_job wrapper from fast_chunks.py
- Inline create_batch_job calls in fast.py (3 sites)
- Reduces indirection, no functional change

Per code review feedback from PR #1202.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
AkhileshNegi and others added 4 commits September 21, 2026 12:15
Condense 2-line comments:
- Run-level input resolution behavior
- Judge call cost grouping

No unused imports found (generate_run_ai_summary used at line 728).
No now_/now naming issues in cron.py.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Remove unused Any import from fast_chunks.py
- Change question_id: Any → int | None in build_response_result
- Make retry_openai_call decorator generic (ParamSpec/TypeVar) so decorated functions preserve return types instead of erasing to Any

Type checking: 0 errors. Decorator genericity fixes return-type erasure on _create_response and _create_embedding.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Define proper types for evaluation result structures:
- ResponseResult: Stage 1 response evaluation result
- EmbeddingResult: Stage 2 embedding pair result

Replace dict[str, Any] with typed structures in:
- fast_results.py builders
- fast.py function signatures

Improves type safety and code clarity for API response handling.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@AkhileshNegi
AkhileshNegi merged commit cf1cc2f into main Sep 21, 2026
6 checks passed
@AkhileshNegi
AkhileshNegi deleted the refactor/evals2.0 branch September 21, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants