fix(database): replace SQL JSON subscript with Python-side aggregation in pre-OTEL leaderboard - #2730
Open
Aftabbs wants to merge 5 commits into
Open
Conversation
…n in pre-OTEL leaderboard cost_json and perf_json are stored as TEXT (TYPE_JSON = Text in orm.py), so SQLAlchemy's column["key"].as_float() raises NotImplementedError at statement construction time. The pre-OTEL leaderboard path hit this on every call when TRULENS_OTEL_TRACING is not set. Instead of database-level JSON extraction (dialect-specific and broken on Text columns), fetch individual record rows with the raw text fields and aggregate in Python using the existing _extract_tokens_and_cost and _extract_latency helpers. The output schema (Records, Total Tokens, Average Latency (s), Total Cost (USD)) is unchanged. Fixes truera#2729
joshreini1
requested changes
Aug 27, 2026
Collaborator
There was a problem hiding this comment.
OTEL has been enabled by default for more than a year and its aggregation path already works. This PR should keep its scope on the legacy non-OTEL path, but implement that aggregation in SQL rather than moving it into frontend Python. SQL aggregation is materially more scalable and avoids transferring raw records to the frontend for processing.
Ruff F401: core_schema and base_schema imported but not used in test_leaderboard_with_records_aggregates_correctly. Move datetime import to module level. Fix multi-line dict literal to one key-per-line style for ruff-format compliance.
Replace Python-side pandas groupby with database-level aggregation in _get_leaderboard_aggregates_pre_otel. cost_json/perf_json are stored as TYPE_JSON = Text. Use _json_path_expr (json_extract on SQLite/MySQL, json_extract_path_text on PostgreSQL) to extract scalar values at the database level. Aggregation now uses: - SUM(CAST(json_extract(cost_json, '$.n_tokens') AS FLOAT)) for tokens - SUM(CAST(json_extract(cost_json, '$.cost') AS FLOAT)) for cost - COUNT(DISTINCT record_id) for record count - AVG((julianday(end_time) - julianday(start_time)) * 86400) for latency (SQLite), or AVG(EXTRACT(EPOCH FROM ...)) for PostgreSQL This avoids transferring O(n_records) rows to the frontend for Python-side processing, which is the scalable behaviour joshreini1 requested.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
get_leaderboard_aggregates()crashes withNotImplementedErrorwheneverTRULENS_OTEL_TRACINGis not enabled (the default for most users).The root cause is in
_get_leaderboard_aggregates_pre_otel: the SQL query uses SQLAlchemy's[]subscript operator to extract values fromcost_jsonand references a non-existentRecord.latencycolumn:cost_jsonandperf_jsonare declared asTYPE_JSON = Text(orm.py:35), so they are plain TEXT columns. SQLAlchemy raisesNotImplementedErrorfor[]subscript access onTextcolumns at statement construction time, before any query ever hits the database.Fixes #2729.
Fix
Replace the broken SQL-level aggregation with Python-side aggregation using the existing module-level helpers
_extract_tokens_and_costand_extract_latency(already used by theget_records_and_feedbackcode path):record_stmtnow selects individual rows with rawcost_jsonandperf_jsontext columns (no subscript, no missinglatencycolumn)._extract_tokens_and_cost/_extract_latency.pandas.groupby(...).agg(...)for the per-app aggregation that was previously done in SQL.The output schema (
Records,Total Tokens,Average Latency (s),Total Cost (USD)) is unchanged.Tests
Added
tests/unit/test_leaderboard_pre_otel.pywith three test cases:Records,Total Tokens,Total Cost (USD), andAverage Latency (s)are computed correctly.app_namefilter – only the matching app's rows are returned.All tests run against an in-memory SQLite database, matching the existing test pattern used in
test_dashboard_utils.py.Checklist
orm.py(TYPE_JSON = Text)