Skip to content

Conversation

@benjibc
Copy link
Contributor

@benjibc benjibc commented Jan 11, 2026

Summary

When opening the logs UI with a filterConfig URL parameter (e.g., filtering by invocation_id), the backend now filters data server-side instead of sending all logs to the frontend for client-side filtering.

Problem

ep logs was very slow when opening the UI with rollouts because:

  1. The backend read ALL logs from the SQLite database
  2. Serialized and sent everything over WebSocket
  3. Frontend then filtered client-side

This was especially painful with large databases.

Solution

Backend Changes

  • Database layer (sqlite_evaluation_row_store.py): Added invocation_ids and limit parameters to read_rows() using SQLite JSON extraction
  • Dataset logger (sqlite_dataset_logger_adapter.py, dataset_logger.py, __init__.py): Updated read() method signature
  • WebSocket server (logs_server.py):
    • Default limit of 1000 rows for initial load
    • Parse invocation_ids and limit from WebSocket URL query params
    • Track per-connection filters and only broadcast relevant messages
    • Order results by most recent first

Frontend Changes

  • config.ts: Added extractInvocationIdsFromUrl() to parse invocation IDs from URL filterConfig
  • App.tsx: WebSocket connection now passes invocation filter as query params

How It Works

  1. User opens URL: http://localhost:8000?filterConfig=[{"logic":"AND","filters":[{"field":"$.execution_metadata.invocation_id","operator":"equals","value":"inv-123"}]}]
  2. Frontend extracts inv-123 from filter config
  3. Connects to WebSocket: ws://localhost:8000/ws?invocation_ids=inv-123
  4. Backend only queries and sends matching rows (up to 1000)
  5. Real-time updates also filtered per-connection

Testing

  • Added tests for invocation_ids filtering in database
  • Added tests for limit parameter
  • Added tests for WebSocket connection with filters
  • All existing tests pass

Note

Improves log loading performance by filtering and limiting data at the source.

  • Extend DatasetLogger.read() to accept rollout_id, invocation_ids, and limit; implement in sqlite_dataset_logger_adapter.py and sqlite_evaluation_row_store.py (JSON extract on $.execution_metadata.invocation_id, ordered most-recent-first, optional limit)
  • logs_server.py: WebSocket accepts invocation_ids and limit query params, sends only matching initial rows (default limit=1000), and filters real-time broadcasts per-connection
  • Frontend: config.ts adds extractInvocationIdsFromUrl() and augments getWebSocketUrl() to include invocation_ids; App.tsx passes extracted IDs when connecting
  • Tests: add coverage for DB filtering/limits and WebSocket filtering behavior

Written by Cursor Bugbot for commit 84949cd. This will update automatically on new commits. Configure here.

When opening the logs UI with a filterConfig URL parameter (e.g., filtering
by invocation_id), the backend now filters data server-side instead of
sending all logs to the frontend for client-side filtering.

Changes:
- Add invocation_ids and limit parameters to SqliteEvaluationRowStore.read_rows()
- Add server-side filtering using SQLite JSON extraction
- Default limit of 1000 rows for initial WebSocket load
- WebSocket endpoint parses invocation_ids from URL query params
- Frontend extracts invocation_ids from URL filterConfig and passes to WebSocket
- Real-time broadcast messages filtered per-connection based on subscribed invocation_ids
- Order results by most recent first (DESC by rowid)

This significantly speeds up ep logs when viewing filtered results by
avoiding loading all historical data.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84949cd665

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 26 to +31
@abstractmethod
def read(self, row_id: Optional[str] = None) -> List["EvaluationRow"]:
def read(
self,
rollout_id: Optional[str] = None,
invocation_ids: Optional[List[str]] = None,
limit: Optional[int] = None,

Choose a reason for hiding this comment

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

P2 Badge Keep DatasetLogger implementations aligned with new read() args

The abstract DatasetLogger.read signature now includes rollout_id, invocation_ids, and limit, but LocalFSDatasetLoggerAdapter still only defines read(self, row_id=...). Any caller that treats a LocalFSDatasetLoggerAdapter as a DatasetLogger and passes the new keywords (for example, read(invocation_ids=[...]) or read(rollout_id=...)) will now hit a TypeError for unexpected keyword arguments. To preserve compatibility with existing adapters, they should be updated to accept the new parameters (even if they ignore them).

Useful? React with 👍 / 👎.

try:
logger.debug(f"[WEBSOCKET_SEND] Preparing to send to connection {i}")
tasks.append(connection.send_text(text))
task_connections.append(connection)
Copy link

Choose a reason for hiding this comment

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

Failed connection preparation skips cleanup

Low Severity

In _send_text_to_filtered_connections, when an exception occurs during coroutine preparation (lines 231-232), the connection is no longer added to failed_connections for cleanup. The original code added failing connections to failed_connections in the except block, but this was removed during refactoring. Now failed_connections is initialized after the loop (line 235), making it impossible to track preparation failures. Connections that fail during preparation will remain in active_connections and _connection_filters until they fail during an actual send attempt.

Additional Locations (1)

Fix in Cursor Fix in Web

eligible_connections.append(conn)
elif row_invocation_id is not None and row_invocation_id in conn_filter:
# Row's invocation_id matches connection's filter
eligible_connections.append(conn)
Copy link

Choose a reason for hiding this comment

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

Empty invocation_ids filter causes inconsistent behavior

Low Severity

Empty invocation_ids list is handled inconsistently between initial load and real-time updates. In read_rows(), the condition len(invocation_ids) > 0 treats an empty list as "no filter" (returns all rows). But in _send_text_to_filtered_connections(), the check conn_filter is None is False for [], and row_invocation_id in [] is always False, so the connection receives no updates. This causes a scenario where a WebSocket connection with ?invocation_ids=, receives all initial logs but never receives real-time broadcasts.

Additional Locations (1)

Fix in Cursor Fix in Web

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