-
Notifications
You must be signed in to change notification settings - Fork 10
feat(evaluation): v2 native LLM-as-judge (ground truth) + Langfuse-free #1055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ayush8923
wants to merge
10
commits into
main
Choose a base branch
from
feat/three-metric-evals
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
452985a
feat(evaluation): v2 native LLM-as-judge (ground truth) + Langfuse-fr…
Ayush8923 f04ff3c
fix(*): syncup changes
Ayush8923 2c4e9c7
fix(*): handle the langfuse related things for v2 evals
Ayush8923 fec8331
fix(evlas): update the migration for evals run
Ayush8923 01e79f0
fix(*): cleanups
Ayush8923 091aa9e
Merge branch 'main' into feat/three-metric-evals
Ayush8923 1ac26d9
fix(evals): update the migration
Ayush8923 147e5fd
fix(*): cleanups
Ayush8923 c3367ae
fix(*): remove the unwanted files
Ayush8923 b6328ed
fix(*): cleanups
Ayush8923 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
backend/app/alembic/versions/075_add_judge_columns_to_evaluation_run.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| """Add native LLM-as-judge columns to evaluation_run | ||
|
|
||
| Revision ID: 075 | ||
| Revises: 074 | ||
| Create Date: 2026-07-16 00:00:00.000000 | ||
|
|
||
| The v2 judged fast-eval run stores its judge state on the existing evaluation_run | ||
| row rather than a new table. The chunked aggregate task only knows an eval_run_id, | ||
| so the judge intent (is_judge_run) must be durable on the row for the aggregate to | ||
| read at judge time. per_item_ground_truth mirrors per_item_scores as Kaapi's native | ||
| per-row store (v2 never syncs to Langfuse). Judging is system-config only — always | ||
| the fallback model + built-in prompt — so no per-run judge config is persisted. | ||
|
|
||
| Both columns are nullable with default NULL: v1 and pre-feature runs carry no judge | ||
| data, so no backfill is needed. | ||
| """ | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
| from sqlalchemy.dialects.postgresql import JSONB | ||
|
|
||
| revision = "075" | ||
| down_revision = "074" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade(): | ||
| op.add_column( | ||
| "evaluation_run", | ||
| sa.Column( | ||
| "is_judge_run", | ||
| sa.Boolean(), | ||
| nullable=True, | ||
| comment=( | ||
| "True for v2 runs that run the native LLM-as-judge (and skip the " | ||
| "Langfuse score sync). NULL/False = v1 run, cosine-only, Langfuse-synced" | ||
| ), | ||
| ), | ||
| ) | ||
| op.add_column( | ||
| "evaluation_run", | ||
| sa.Column( | ||
| "per_item_ground_truth", | ||
| JSONB(), | ||
| nullable=True, | ||
| comment=( | ||
| "Durable {ref: score} map of the Adherence to Ground Truth judge " | ||
| "scores (ref = trace_id when traced, else item_id); Kaapi's own store" | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def downgrade(): | ||
| op.drop_column("evaluation_run", "per_item_ground_truth") | ||
| op.drop_column("evaluation_run", "is_judge_run") |
18 changes: 18 additions & 0 deletions
18
backend/app/api/docs/evaluation/create_evaluation_dataset_v2.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| Upload a CSV of golden Q&A pairs for evaluation, stored natively by Kaapi with no | ||
| Langfuse dataset (v2). | ||
|
|
||
| Same multipart shape as the v1 dataset upload (`file`, `dataset_name`, | ||
| `description`, `duplication_factor`), but the dataset is stored in object storage | ||
| (S3) only: the row is created with `langfuse_dataset_id` null and the CSV holds | ||
| exactly the **original** rows — no physical duplication. The `duplication_factor` | ||
| is recorded in `dataset_metadata` (with a run-time-duplication marker) and applied | ||
| when the run executes, so an 8-row CSV with factor 5 evaluates 40 rows. | ||
|
|
||
| **Response:** `APIResponse[DatasetUploadResponse]`, same shape as v1 with | ||
| `langfuse_dataset_id` null. `original_items` is the stored row count and | ||
| `total_items` is `original_items × duplication_factor` (the count the run will | ||
| produce, not stored rows). `eligible_for_fast` reflects only the unique-row size | ||
| cap (`EVAL_FAST_MAX_UNIQUE_ROWS`). | ||
|
|
||
| **CSV format:** required columns `question`, `answer`; optional `category`. Rows | ||
| with a missing question or answer are skipped. Maximum upload size is 1 MB. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| Start a v2 evaluation run. Replicates the v1 `POST /api/v1/evaluations` request | ||
| body with Kaapi's native LLM-as-Judge built in — v1 is left unchanged. | ||
|
|
||
| v2 runs are **always fast** and always judged (there is no `run_mode`; batch is | ||
| deferred to a later phase). Every scoreable row is automatically judged (no opt-in | ||
| flag) on **Adherence to Ground Truth**: an LLM judge scores whether the answer | ||
| conveys the same correct information as the dataset's golden answer (0–1, with | ||
| reasoning). Scores, the durable per-row map (`per_item_ground_truth`), and the | ||
| `ground_truth_judge` cost stage are stored natively by Kaapi. v2 runs compute **no | ||
| cosine similarity** and do **not** touch Langfuse. | ||
|
|
||
| Judging is system-config only: the judge always uses the configured model | ||
| (`EVAL_JUDGE_MODEL`, default `gpt-5-mini`) and the built-in ground-truth prompt. | ||
| There is no per-run or ad-hoc judge configuration. | ||
|
|
||
| ## Example | ||
|
|
||
| ```json | ||
| { | ||
| "dataset_id": 123, | ||
| "experiment_name": "judge-smoke-1", | ||
| "config_id": "f54f0d67-4817-4103-9fdf-b74b3d46733e", | ||
| "config_version": 1 | ||
| } | ||
| ``` | ||
|
|
||
| ## Error responses | ||
|
|
||
| | Status | Code | When | | ||
| | --- | --- | --- | | ||
| | 409 | `run_name_already_exists` | A run with the same `experiment_name` already exists for this (organization, project) | | ||
| | 422 | — | An unsupported config type / oversized dataset | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """v2 Langfuse-free evaluation dataset upload route. | ||
|
|
||
| Replica of the v1 dataset upload with the same multipart shape and response, but | ||
| it creates no Langfuse dataset and stores only the original items — duplication is | ||
| recorded in metadata and applied at run time. | ||
| """ | ||
|
|
||
| import logging | ||
|
|
||
| from fastapi import APIRouter, Depends, File, Form, UploadFile | ||
|
|
||
| from app.api.deps import AuthContextDep, SessionDep | ||
| from app.api.permissions import Permission, require_permission | ||
| from app.api.routes.evaluations.dataset import _dataset_to_response | ||
| from app.core.rate_monitor import monitor_rate | ||
| from app.models.evaluation import DatasetUploadResponse | ||
| from app.services.evaluations import upload_dataset_v2, validate_csv_file | ||
| from app.utils import APIResponse, load_description | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter(prefix="/evaluations/datasets", tags=["Evaluation v2"]) | ||
|
|
||
|
|
||
| @router.post( | ||
| "", | ||
| description=load_description("evaluation/create_evaluation_dataset_v2.md"), | ||
| response_model=APIResponse[DatasetUploadResponse], | ||
| dependencies=[ | ||
| Depends(require_permission(Permission.REQUIRE_PROJECT)), | ||
| Depends(monitor_rate("evaluations")), | ||
| ], | ||
| ) | ||
| async def upload_dataset_v2_route( | ||
| session: SessionDep, | ||
| auth_context: AuthContextDep, | ||
| file: UploadFile = File( | ||
| ..., description="CSV file with 'question' and 'answer' columns" | ||
| ), | ||
| dataset_name: str = Form(..., description="Name for the dataset"), | ||
| description: str | None = Form(None, description="Optional dataset description"), | ||
| duplication_factor: int = Form( | ||
| default=1, | ||
| ge=1, | ||
| le=5, | ||
| description="Run-time duplication per item (min: 1, max: 5)", | ||
| ), | ||
| ) -> APIResponse[DatasetUploadResponse]: | ||
| """Upload a Langfuse-free evaluation dataset (v2).""" | ||
| csv_content = await validate_csv_file(file) | ||
|
|
||
| dataset = upload_dataset_v2( | ||
| session=session, | ||
| csv_content=csv_content, | ||
| dataset_name=dataset_name, | ||
| description=description, | ||
| duplication_factor=duplication_factor, | ||
| organization_id=auth_context.organization_.id, | ||
| project_id=auth_context.project_.id, | ||
| ) | ||
|
|
||
| return APIResponse.success_response(data=_dataset_to_response(dataset)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| """v2 evaluation run trigger — replica of the v1 trigger plus native judging.""" | ||
|
|
||
| import logging | ||
| from uuid import UUID | ||
|
|
||
| from asgi_correlation_id import correlation_id | ||
| from fastapi import APIRouter, Body, Depends | ||
|
|
||
| from app.api.deps import AuthContextDep, SessionDep | ||
| from app.api.permissions import Permission, require_permission | ||
| from app.core.rate_monitor import monitor_rate | ||
| from app.models.evaluation import EvaluationRunPublic | ||
| from app.services.evaluations.judge import validate_and_start_judged_evaluation | ||
| from app.utils import APIResponse, load_description | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter(prefix="/evaluations", tags=["Evaluation v2"]) | ||
|
|
||
|
|
||
| @router.post( | ||
| "", | ||
| description=load_description("evaluation/create_evaluation_v2.md"), | ||
| response_model=APIResponse[EvaluationRunPublic], | ||
| dependencies=[ | ||
| Depends(require_permission(Permission.REQUIRE_PROJECT)), | ||
| Depends(monitor_rate("evaluations")), | ||
| ], | ||
| ) | ||
| def evaluate_v2( | ||
| session: SessionDep, | ||
| auth_context: AuthContextDep, | ||
| dataset_id: int = Body(..., description="ID of the evaluation dataset"), | ||
| experiment_name: str = Body( | ||
| ..., description="Name for this evaluation experiment/run" | ||
| ), | ||
| config_id: UUID = Body(..., description="Stored config ID"), | ||
| config_version: int = Body(..., ge=1, description="Stored config version"), | ||
| ) -> APIResponse[EvaluationRunPublic]: | ||
| """Start a v2 evaluation run. | ||
|
|
||
| v2 runs are always fast and judged on Adherence to Ground Truth; there is no | ||
| `run_mode` — batch judging is deferred to a later phase. | ||
| """ | ||
|
|
||
| eval_run = validate_and_start_judged_evaluation( | ||
| session=session, | ||
| dataset_id=dataset_id, | ||
| run_name=experiment_name, | ||
| config_id=config_id, | ||
| config_version=config_version, | ||
| organization_id=auth_context.organization_.id, | ||
| project_id=auth_context.project_.id, | ||
| trace_id=correlation_id.get() or "N/A", | ||
| ) | ||
| return APIResponse.success_response(data=eval_run) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.