-
Notifications
You must be signed in to change notification settings - Fork 10
feat(assessment): Persist batch results and deliver them in callback metadata #1199
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9e7f6c4
feat(assessment): durable result files and own submission table
vprashrex 53a5d02
Merge branch 'main' into chore/assessment-config-fixes
vprashrex bf8c3ec
fix(assessment): align tests with the submission rename
vprashrex 55820af
test(assessment): cover the submission crud and storage round trip
vprashrex a2a5bb1
test(assessment): cover the submission crud and storage round trip
vprashrex b800caf
Enhance assessment API with polling and detailed result retrieval
vprashrex 44853e0
Merge branch 'main' into chore/assessment-config-fixes
vprashrex ddf8b9f
fix(assessment): update documentation and improve handling of BATCH a…
vprashrex 4c40fe8
feat(assessment): add migration for assessment_submission table and u…
vprashrex 3f12dc4
fix(assessment): improve error handling in set_result_files and updat…
vprashrex 038dd77
fix(assessment): update input schema validation and documentation for…
vprashrex 0cebe44
fix(assessment): update input schema validation rules and documentati…
vprashrex e1ccd46
fix(assessment): update task handling and improve submission row stre…
vprashrex 76e6094
fix(assessment): update test for strict column validation in submissi…
vprashrex 46dfd81
fix(assessment): update documentation and error handling for RUN and …
vprashrex a1e4afe
Refactor assessment submission handling and improve error management
vprashrex 77ff9cc
fix(assessment): refactor normalize_llm_text function and update rela…
vprashrex 005ddaa
fix(assessment): improve error handling during row storage and enhanc…
vprashrex 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
162 changes: 162 additions & 0 deletions
162
backend/app/alembic/versions/084_assessment_submissions_and_result_files.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,162 @@ | ||
| """Assessment submissions table, submission/result-file pointers, provider error file id | ||
|
|
||
| Revision ID: 084 | ||
| Revises: 083 | ||
| Create Date: 2026-09-09 00:00:00.000000 | ||
|
|
||
| Assessment submissions leave `evaluation_dataset`, whose type-agnostic name uniqueness | ||
| let an eval dataset block an assessment one. Multi-MB payloads leave Postgres too: | ||
| `submission_input` and `result_files` hold s3:// urls, and `provider_error_file_id` | ||
| keeps OpenAI's error dump fetchable after the poll that surfaced it. `dataset_id` is | ||
| dropped without a backfill: no assessment rows exist in any environment yet. | ||
| """ | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
| from sqlalchemy.dialects import postgresql | ||
|
|
||
| revision = "084" | ||
| down_revision = "083" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
| RESULT_FILES_CHECK = "ck_assessment_result_files_is_object" | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| op.create_table( | ||
| "assessment_submission", | ||
| sa.Column( | ||
| "id", | ||
| postgresql.UUID(as_uuid=True), | ||
| primary_key=True, | ||
| comment="Unique identifier for the submission", | ||
| ), | ||
| sa.Column( | ||
| "name", | ||
| sa.String(), | ||
| nullable=False, | ||
| comment="Sanitized name; the object key is derived from it", | ||
| ), | ||
| sa.Column( | ||
| "description", sa.String(), nullable=True, comment="Optional description" | ||
| ), | ||
| sa.Column( | ||
| "object_store_url", | ||
| sa.String(), | ||
| nullable=False, | ||
| comment="Object-store url of the uploaded file; its suffix gives the format", | ||
| ), | ||
| sa.Column( | ||
| "total_items", | ||
| sa.Integer(), | ||
| nullable=False, | ||
| server_default="0", | ||
| comment="Row count, excluding the header", | ||
| ), | ||
| sa.Column( | ||
| "organization_id", | ||
| sa.Integer(), | ||
| sa.ForeignKey("organization.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| ), | ||
| sa.Column( | ||
| "project_id", | ||
| sa.Integer(), | ||
| sa.ForeignKey("project.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| ), | ||
| sa.Column("inserted_at", sa.DateTime(), nullable=False), | ||
| sa.Column("updated_at", sa.DateTime(), nullable=False), | ||
| sa.UniqueConstraint( | ||
| "name", | ||
| "organization_id", | ||
| "project_id", | ||
| name="uq_assessment_submission_name_org_project", | ||
| ), | ||
| ) | ||
| op.create_index("ix_assessment_submission_name", "assessment_submission", ["name"]) | ||
|
|
||
| op.add_column( | ||
| "assessment", | ||
| sa.Column( | ||
| "result_files", | ||
| postgresql.JSONB(astext_type=sa.Text()), | ||
| nullable=False, | ||
| server_default=sa.text("'{}'::jsonb"), | ||
| comment=( | ||
| "Result-file kind (results / errors / <stage>_results) to " | ||
| "{object_store_url} for every provider batch dump held; raw s3:// in the " | ||
| "column, presigned per delivery in the BATCH callback" | ||
| ), | ||
| ), | ||
| ) | ||
| op.add_column( | ||
| "assessment", | ||
| sa.Column( | ||
| "submission_input", | ||
| sa.String(), | ||
| nullable=True, | ||
| comment=( | ||
| "Object-store url of the API-client BATCH submission rows " | ||
| "(submission.jsonl); the rows are never stored in this table" | ||
| ), | ||
| ), | ||
| ) | ||
| op.drop_column("assessment", "dataset_id") | ||
| op.add_column( | ||
| "assessment", | ||
| sa.Column( | ||
| "submission_id", | ||
| postgresql.UUID(as_uuid=True), | ||
| sa.ForeignKey("assessment_submission.id", ondelete="SET NULL"), | ||
| nullable=True, | ||
| comment=( | ||
| "Uploaded submission the rows came from; set by RUN and by a BATCH " | ||
| "submitted with `submission_doc_id`. NULL when BATCH sent rows inline" | ||
| ), | ||
| ), | ||
| ) | ||
| op.create_index("ix_assessment_submission_id", "assessment", ["submission_id"]) | ||
|
|
||
| op.add_column( | ||
| "batch_job", | ||
| sa.Column( | ||
| "provider_error_file_id", | ||
| sa.String(), | ||
| nullable=True, | ||
| comment=( | ||
| "Provider's error file ID (OpenAI only; Anthropic and Gemini report " | ||
| "per-item errors inline)" | ||
| ), | ||
| ), | ||
| ) | ||
| op.create_check_constraint( | ||
| RESULT_FILES_CHECK, | ||
| "assessment", | ||
| "jsonb_typeof(result_files) = 'object'", | ||
| ) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_constraint(RESULT_FILES_CHECK, "assessment", type_="check") | ||
| op.drop_column("batch_job", "provider_error_file_id") | ||
|
|
||
| op.drop_index("ix_assessment_submission_id", table_name="assessment") | ||
| op.drop_column("assessment", "submission_id") | ||
| op.add_column( | ||
| "assessment", | ||
| sa.Column( | ||
| "dataset_id", | ||
| sa.Integer(), | ||
| sa.ForeignKey("evaluation_dataset.id", ondelete="SET NULL"), | ||
| nullable=True, | ||
| comment="External dataset (RUN); binding lives in `input`", | ||
| ), | ||
| ) | ||
|
|
||
| op.drop_column("assessment", "submission_input") | ||
| op.drop_column("assessment", "result_files") | ||
|
|
||
| op.drop_index("ix_assessment_submission_name", table_name="assessment_submission") | ||
| op.drop_table("assessment_submission") | ||
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 |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| Export results for all child runs under an assessment. | ||
| Export results for all child runs under a RUN assessment. | ||
|
|
||
| For `json`, returns a flat list in the API response. For `csv`/`xlsx`, | ||
| returns one file for a single run or a ZIP archive when multiple runs exist. | ||
|
|
||
| Returns `422` for a BATCH assessment: its rows are served by | ||
| `GET /assessments/{assessment_id}`. |
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,21 @@ | ||
| Fetch a BATCH assessment's status and every row it has produced so far. | ||
|
|
||
| Safe to poll while the run is in flight. `items` always holds exactly `total_items` | ||
| entries, in submission order, including placeholders for rows the provider has not | ||
| returned yet (`output.assessment` is `null`) and for rows a pre-filter gated out. | ||
|
|
||
| Each row carries: | ||
|
|
||
| - `row_index` — position in the original submission; the stable correlator. | ||
| - `input` — the submitted row, echoed back only when `include_input=true`. That flag | ||
| re-reads the stored submission from object storage on every call, so leave it off in | ||
| a tight poll loop and set it once the run is terminal. `null` if the stored submission | ||
| could not be read. | ||
| - `output` — identical in shape to the webhook payload's item, so one parser serves both. | ||
| - `error` — the provider's error for that row, when it failed. | ||
|
|
||
| Stop polling once `status` is `COMPLETED`, `COMPLETED_WITH_ERRORS` or `FAILED`. On a | ||
| failed run, `error` carries the reason. | ||
|
|
||
| Returns 404 when the assessment does not exist in this project, and 422 when it is not | ||
| a BATCH assessment. |
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 |
|---|---|---|
| @@ -1,3 +1,14 @@ | ||
| List assessments runs for the current organization/project. | ||
| List this project's BATCH assessments, newest first. | ||
|
|
||
| Each record includes aggregate status counters across its child runs. | ||
| Each row carries where the run is and what it was pinned to — status, stage, the config | ||
| id and version, and the row count. It deliberately carries **no per-row counts or | ||
| results**: those need the provider dump streamed back from object storage, which a list | ||
| must not pay for. Fetch `GET /assessments/{assessment_id}` for a single run's rows. | ||
|
|
||
| **Filtering** | ||
|
|
||
| - `config_id` — return only the runs pinned to that config. Omit it for every run. | ||
| - `version` — narrow further to one version of that config. Applied only with | ||
| `config_id`; omit it for every version. | ||
|
|
||
| `limit` defaults to 50 (max 100) and `offset` pages through. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,13 +6,15 @@ create a new version under the same configuration with an incremented version nu | |
| Version numbers are automatically incremented sequentially (1, 2, 3, etc.) | ||
| and cannot be manually set or skipped. | ||
|
|
||
| ## Examples | ||
| The `config_blob` shape follows the parent config: the `completion` shape for a | ||
| `default` config, the `assessment` shape for an `ASSESSMENT` config. How the body is | ||
| applied differs between the two, so read the matching section below. | ||
|
|
||
| Send only the fields you want to change. The `config_blob` shape follows the parent | ||
| config: the `completion` shape for a `default` config, the `assessment` shape for an | ||
| `ASSESSMENT` config. | ||
| ## default configs — partial update | ||
|
|
||
| Send only the fields you want to change. They are merged onto the latest version, so | ||
| anything you omit is carried forward. | ||
|
|
||
| **When the parent config is `default` (completion shape):** | ||
| ```json | ||
| { | ||
| "config_blob": { | ||
|
|
@@ -24,24 +26,44 @@ config: the `completion` shape for a `default` config, the `assessment` shape fo | |
| } | ||
| ``` | ||
|
|
||
| **When the parent config is `ASSESSMENT` (assessment shape):** | ||
| ## ASSESSMENT configs — full blob | ||
|
|
||
| Send the **whole** `config_blob` every time. It replaces the previous version rather than | ||
| merging onto it, so a column dropped from `input_schema`, a removed `json_output_schema` | ||
| field, or an omitted `pre_filters` block is genuinely gone in the new version. A partial | ||
| body is rejected with `422`, because `input_schema` and `assessment` are mandatory. | ||
|
|
||
| ```json | ||
| { | ||
| "config_blob": { | ||
| "input_schema": { | ||
| "rubric": { "type": "text" }, | ||
| "answer": { "type": "text", "strict": true } | ||
| }, | ||
| "pre_filters": { | ||
| "topic_relevance": { | ||
| "params": { "model": "gpt-4o", "instructions": "Is this a Class 7 answer sheet?" } | ||
| "provider": "openai", | ||
| "params": { "model": "gpt-4o", "instructions": "Is this a Class 7 answer sheet?" }, | ||
| "stop_on_fail": true | ||
| } | ||
| }, | ||
| "assessment": { | ||
| "params": { "model": "gpt-4o" } | ||
| "provider": "openai", | ||
| "type": "text", | ||
| "params": { | ||
| "model": "gpt-4o", | ||
| "instructions": "You are an AI Assessment Evaluator ...", | ||
| "submission": "Grade this answer against the rubric: {rubric}\n\nAnswer: {answer}" | ||
| } | ||
| } | ||
| }, | ||
| "commit_message": "Switch grading model" | ||
| "commit_message": "Drop the unused columns" | ||
| } | ||
| ``` | ||
|
|
||
| ## Important | ||
| - This endpoint accepts partial updates using dict[str, Any] for config_blob. | ||
| - Only the fields that need to be updated should be provided. | ||
| - The `type` field is inherited from the existing configuration and cannot be changed. Provider and model can change between versions. | ||
| - Every field inside `config_blob` can change between versions, including provider and model. | ||
| - `tag` belongs to the parent configuration and is never part of a version body. | ||
| - `type` is inherited from the existing configuration and cannot be changed. | ||
|
Comment on lines
+65
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Scope the mutability statement to mutable fields. Line 65 says every 🤖 Prompt for AI Agents |
||
| - A run pins the `config_id` and `version` it was submitted with, so a new version never | ||
| alters a run that is already in flight or finished. | ||
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.