-
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
14
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
14 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 902575b
feat(docs): Improve knowledgebase adherence (#1059)
AkhileshNegi 086cbd3
feat(metrics): Adherence To Prompt (#1058)
Ayush8923 f3077a0
minor cleanups
AkhileshNegi 516a989
cleanups
AkhileshNegi 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
36 changes: 36 additions & 0 deletions
36
backend/app/alembic/versions/075_add_judge_column_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,36 @@ | ||
| """Add is_judge_run to evaluation_run | ||
|
|
||
| Revision ID: 075 | ||
| Revises: 074 | ||
| Create Date: 2026-07-16 00:00:00.000000 | ||
|
|
||
| The chunked aggregate task only knows an eval_run_id, so the judge intent has to be | ||
| durable on the run row for it to read at judge time. | ||
| """ | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
|
|
||
| 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" | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def downgrade(): | ||
| 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,36 @@ | ||
| 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) by one combined LLM-judge call scoring each applicable metric in [0, 1] with | ||
| reasoning: **Adherence to Ground Truth** (answer conveys the same correct | ||
| information as the golden answer), **Adherence to Prompt** (answer obeys the | ||
| assistant's configured instructions; applies only when the run resolves a config | ||
| prompt), and **Adherence to Knowledge Base** (groundedness of the answer against | ||
| the retrieved chunks; applies only to rows that retrieved chunks). Per-row scores + | ||
| reasoning are stored natively by Kaapi in the `score_trace_url` trace unit; the | ||
| single `judge` cost stage covers the one combined call. 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 per-metric prompts. | ||
| 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,61 @@ | ||
| """v2 Langfuse-free evaluation dataset upload route. | ||
|
|
||
| Same multipart shape and response, but no Langfuse dataset is created and only | ||
| the original items are stored — duplication is metadata, 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,57 @@ | ||
| """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.fast import validate_and_start_fast_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. | ||
|
|
||
| Always fast and judged; there is no `run_mode` — batch judging is deferred. | ||
| Judging always runs (`is_judge_run=True`); there is no per-run judge config. | ||
| """ | ||
|
|
||
| eval_run = validate_and_start_fast_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", | ||
| is_judge_run=True, | ||
| ) | ||
| 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
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.