Implementation:Langfuse Langfuse CommentFilterService
| Knowledge Sources | |
|---|---|
| Domains | Filtering, Comments, Data Access |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
The commentFilterService processes comment-based filter conditions (comment count and comment content) from the UI filter state and converts them into object ID inclusion/exclusion filters compatible with ClickHouse queries.
Description
This module bridges the gap between PostgreSQL-stored comments and ClickHouse-stored traces/observations/sessions. Since comments live in PostgreSQL and trace data lives in ClickHouse, filtering by comment properties requires a two-step approach: first query PostgreSQL for matching object IDs, then inject those IDs into the ClickHouse filter state.
The primary export is applyCommentFilters, which:
- Extracts
commentCountandcommentContentfilters from the filter state. - If no comment filters are present, returns the original filter state unchanged.
- For comment count filters, determines whether the filter range includes zero:
- Includes zero (e.g.,
>= 0,<= 5): Uses exclusion logic -- finds objects that EXCEED the upper bound and adds a "none of" filter to exclude them, since objects with zero comments are not in the comments table. - Excludes zero (e.g.,
>= 1,> 0): Uses standard inclusion logic -- finds matching object IDs and adds an "any of" filter.
- Includes zero (e.g.,
- For comment content filters, queries for objects with matching comment text.
- When both count and content filters are present, intersects the results (AND logic).
- Validates that the number of matching object IDs does not exceed
COMMENT_FILTER_THRESHOLD(50,000) to protect ClickHouse from excessively large IN clauses.
The function returns three values:
filterState-- the updated filter state with comment filters replaced by ID-based filtershasNoMatches-- boolean indicating the caller should return an empty resultmatchingIds-- the raw matching IDs (useful for intersection with other ID lists, e.g., metrics endpoint)
Usage
Use this service when you need to:
- Filter traces, sessions, or observations by comment count or comment content in any table listing endpoint.
- Convert user-facing comment filter UI conditions into database-executable filters.
- Handle the zero-comment edge case where objects without comments need to be included in results.
Code Reference
Source Location
- Repository: Langfuse
- File: packages/shared/src/server/services/commentFilterService.ts
- Lines: 1-326
Signature
export const COMMENT_FILTER_THRESHOLD = 50000;
export function validateObjectIdCount(
objectIds: string[],
objectType: CommentObjectType,
): void;
export async function applyCommentFilters(params: {
filterState: z.infer<typeof singleFilter>[];
prisma: PrismaClient;
projectId: string;
objectType: CommentObjectType;
idColumn?: string;
}): Promise<{
filterState: z.infer<typeof singleFilter>[];
hasNoMatches: boolean;
matchingIds: string[] | null;
}>;
Import
import { applyCommentFilters, COMMENT_FILTER_THRESHOLD } from "@langfuse/shared/src/server/services/commentFilterService";
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| filterState | singleFilter[] | Yes | The current filter state from the UI, potentially containing commentCount and commentContent filters |
| prisma | PrismaClient | Yes | Prisma client instance for querying comments in PostgreSQL |
| projectId | string | Yes | The project ID scoping the comment queries |
| objectType | CommentObjectType | Yes | The type of object being filtered (e.g., "TRACE", "SESSION", "OBSERVATION") |
| idColumn | string | No | The column name for object IDs in the resulting filter (defaults to "id") |
Outputs
| Name | Type | Description |
|---|---|---|
| filterState | singleFilter[] | Updated filter state with comment filters replaced by stringOptions ID filters |
| hasNoMatches | boolean | true if comment filters matched zero objects (caller should return empty result)
|
| matchingIds | string[] or null | The matching object IDs, or null if no comment filters were present |
Usage Examples
import { applyCommentFilters } from "@langfuse/shared/src/server/services/commentFilterService";
// In a tRPC endpoint for listing traces
const { filterState, hasNoMatches, matchingIds } = await applyCommentFilters({
filterState: input.filter ?? [],
prisma: ctx.prisma,
projectId: ctx.session.projectId,
objectType: "TRACE",
});
if (hasNoMatches) {
return { traces: [], totalCount: 0 };
}
// Use the updated filterState in the ClickHouse query
const traces = await getTracesTable({
projectId: ctx.session.projectId,
filter: filterState,
orderBy: input.orderBy,
limit: input.limit,
page: input.page,
});
// For metrics endpoint, intersect with another ID list
if (matchingIds) {
const filteredMetricIds = metricTraceIds.filter(
(id) => matchingIds.includes(id)
);
}