From 86023b9bcef75dc361b5cea0778b0ed8c9ba391d Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Fri, 1 Nov 2024 08:45:42 -0700 Subject: [PATCH 1/9] feat: filter sessions on root spans --- app/schema.graphql | 2 +- app/src/pages/project/SessionsTable.tsx | 10 ++- .../ProjectPageSessionsQuery.graphql.ts | 3 +- .../SessionsTableQuery.graphql.ts | 74 +++++++++++-------- .../SessionsTable_sessions.graphql.ts | 14 +++- src/phoenix/server/api/types/Project.py | 12 +++ 6 files changed, 80 insertions(+), 35 deletions(-) diff --git a/app/schema.graphql b/app/schema.graphql index 6736e22be6..a48ef934f7 100644 --- a/app/schema.graphql +++ b/app/schema.graphql @@ -1220,7 +1220,7 @@ type Project implements Node { spanLatencyMsQuantile(probability: Float!, timeRange: TimeRange, filterCondition: String): Float trace(traceId: ID!): Trace spans(timeRange: TimeRange, first: Int = 50, last: Int, after: String, before: String, sort: SpanSort, rootSpansOnly: Boolean, filterCondition: String): SpanConnection! - sessions(timeRange: TimeRange, first: Int = 50, after: String): ProjectSessionConnection! + sessions(timeRange: TimeRange, first: Int = 50, after: String, filterCondition: String): ProjectSessionConnection! """ Names of all available annotations for traces. (The list contains no duplicates.) diff --git a/app/src/pages/project/SessionsTable.tsx b/app/src/pages/project/SessionsTable.tsx index 7d1daf51a0..83f946330e 100644 --- a/app/src/pages/project/SessionsTable.tsx +++ b/app/src/pages/project/SessionsTable.tsx @@ -57,10 +57,15 @@ export function SessionsTable(props: SessionsTableProps) { @argumentDefinitions( after: { type: "String", defaultValue: null } first: { type: "Int", defaultValue: 50 } + filterCondition: { type: "String", defaultValue: null } ) { name - sessions(first: $first, after: $after, timeRange: $timeRange) - @connection(key: "SessionsTable_sessions") { + sessions( + first: $first + after: $after + filterCondition: $filterCondition + timeRange: $timeRange + ) @connection(key: "SessionsTable_sessions") { edges { session: node { id @@ -154,6 +159,7 @@ export function SessionsTable(props: SessionsTableProps) { { after: null, first: PAGE_SIZE, + filterCondition: filterCondition, }, { fetchPolicy: "store-and-network" } ); diff --git a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts index 37dab6d571..a5257059e5 100644 --- a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts +++ b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<2ec99b88de5f2bfca5562d9f2bfa9bc2>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -302,6 +302,7 @@ return { "alias": null, "args": (v4/*: any*/), "filters": [ + "filterCondition", "timeRange" ], "handle": "connection", diff --git a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts index f652065888..3af8b9b8fc 100644 --- a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -16,6 +16,7 @@ export type TimeRange = { }; export type SessionsTableQuery$variables = { after?: string | null; + filterCondition?: string | null; first?: number | null; id: string; timeRange?: TimeRange | null; @@ -37,61 +38,72 @@ var v0 = { "name": "after" }, v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "filterCondition" +}, +v2 = { "defaultValue": 50, "kind": "LocalArgument", "name": "first" }, -v2 = { +v3 = { "defaultValue": null, "kind": "LocalArgument", "name": "id" }, -v3 = { +v4 = { "defaultValue": null, "kind": "LocalArgument", "name": "timeRange" }, -v4 = [ +v5 = [ { "kind": "Variable", "name": "id", "variableName": "id" } ], -v5 = { +v6 = { "kind": "Variable", "name": "after", "variableName": "after" }, -v6 = { +v7 = { + "kind": "Variable", + "name": "filterCondition", + "variableName": "filterCondition" +}, +v8 = { "kind": "Variable", "name": "first", "variableName": "first" }, -v7 = { +v9 = { "alias": null, "args": null, "kind": "ScalarField", "name": "__typename", "storageKey": null }, -v8 = { +v10 = { "alias": null, "args": null, "kind": "ScalarField", "name": "id", "storageKey": null }, -v9 = [ - (v5/*: any*/), +v11 = [ (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), { "kind": "Variable", "name": "timeRange", "variableName": "timeRange" } ], -v10 = [ +v12 = [ { "alias": null, "args": null, @@ -106,7 +118,8 @@ return { (v0/*: any*/), (v1/*: any*/), (v2/*: any*/), - (v3/*: any*/) + (v3/*: any*/), + (v4/*: any*/) ], "kind": "Fragment", "metadata": null, @@ -114,7 +127,7 @@ return { "selections": [ { "alias": null, - "args": (v4/*: any*/), + "args": (v5/*: any*/), "concreteType": null, "kind": "LinkedField", "name": "node", @@ -122,8 +135,9 @@ return { "selections": [ { "args": [ - (v5/*: any*/), - (v6/*: any*/) + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/) ], "kind": "FragmentSpread", "name": "SessionsTable_sessions" @@ -140,26 +154,27 @@ return { "argumentDefinitions": [ (v0/*: any*/), (v1/*: any*/), - (v3/*: any*/), - (v2/*: any*/) + (v2/*: any*/), + (v4/*: any*/), + (v3/*: any*/) ], "kind": "Operation", "name": "SessionsTableQuery", "selections": [ { "alias": null, - "args": (v4/*: any*/), + "args": (v5/*: any*/), "concreteType": null, "kind": "LinkedField", "name": "node", "plural": false, "selections": [ - (v7/*: any*/), + (v9/*: any*/), { "kind": "TypeDiscriminator", "abstractKey": "__isNode" }, - (v8/*: any*/), + (v10/*: any*/), { "kind": "InlineFragment", "selections": [ @@ -172,7 +187,7 @@ return { }, { "alias": null, - "args": (v9/*: any*/), + "args": (v11/*: any*/), "concreteType": "ProjectSessionConnection", "kind": "LinkedField", "name": "sessions", @@ -194,7 +209,7 @@ return { "name": "node", "plural": false, "selections": [ - (v8/*: any*/), + (v10/*: any*/), { "alias": null, "args": null, @@ -230,7 +245,7 @@ return { "kind": "LinkedField", "name": "firstInput", "plural": false, - "selections": (v10/*: any*/), + "selections": (v12/*: any*/), "storageKey": null }, { @@ -240,7 +255,7 @@ return { "kind": "LinkedField", "name": "lastOutput", "plural": false, - "selections": (v10/*: any*/), + "selections": (v12/*: any*/), "storageKey": null }, { @@ -293,7 +308,7 @@ return { "name": "node", "plural": false, "selections": [ - (v7/*: any*/) + (v9/*: any*/) ], "storageKey": null } @@ -330,8 +345,9 @@ return { }, { "alias": null, - "args": (v9/*: any*/), + "args": (v11/*: any*/), "filters": [ + "filterCondition", "timeRange" ], "handle": "connection", @@ -349,16 +365,16 @@ return { ] }, "params": { - "cacheID": "eae7b869a03863c68f10ff3d78c3f46b", + "cacheID": "d7dd13061330467ffb806c85ab235a27", "id": null, "metadata": {}, "name": "SessionsTableQuery", "operationKind": "query", - "text": "query SessionsTableQuery(\n $after: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_2HEEH6\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_2HEEH6 on Project {\n name\n sessions(first: $first, after: $after, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" + "text": "query SessionsTableQuery(\n $after: String = null\n $filterCondition: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_37AmCq\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_37AmCq on Project {\n name\n sessions(first: $first, after: $after, filterCondition: $filterCondition, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "ffd50d06a86cb2efbd63be2f7e658dbf"; +(node as any).hash = "27bb1448e2935d7844a0a83e28f828c9"; export default node; diff --git a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts index 06fe1abde9..40dafa4f56 100644 --- a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<9136b4549ff709dd0d60a353b0c1f804>> * @lightSyntaxTransform * @nogrep */ @@ -71,6 +71,11 @@ return { "kind": "LocalArgument", "name": "after" }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "filterCondition" + }, { "defaultValue": 50, "kind": "LocalArgument", @@ -122,6 +127,11 @@ return { { "alias": "sessions", "args": [ + { + "kind": "Variable", + "name": "filterCondition", + "variableName": "filterCondition" + }, { "kind": "Variable", "name": "timeRange", @@ -296,6 +306,6 @@ return { }; })(); -(node as any).hash = "ffd50d06a86cb2efbd63be2f7e658dbf"; +(node as any).hash = "27bb1448e2935d7844a0a83e28f828c9"; export default node; diff --git a/src/phoenix/server/api/types/Project.py b/src/phoenix/server/api/types/Project.py index 5361962a5a..4a866580bb 100644 --- a/src/phoenix/server/api/types/Project.py +++ b/src/phoenix/server/api/types/Project.py @@ -261,6 +261,7 @@ async def sessions( time_range: Optional[TimeRange] = UNSET, first: Optional[int] = 50, after: Optional[CursorString] = UNSET, + filter_condition: Optional[str] = UNSET, ) -> Connection[ProjectSession]: table = models.ProjectSession stmt = select(table).filter_by(project_id=self.id_attr) @@ -272,6 +273,17 @@ async def sessions( if after: cursor = Cursor.from_string(after) stmt = stmt.where(table.id < cursor.rowid) + if filter_condition: + span_filter = SpanFilter(condition=filter_condition) + subq = span_filter( + ( + stmt.with_only_columns(distinct(table.id).label("id")) + .join_from(table, models.Trace) + .join_from(models.Trace, models.Span) + .where(models.Span.parent_id.is_(None)) + ) + ).subquery() + stmt = stmt.join(subq, table.id == subq.c.id) if first: stmt = stmt.limit( first + 1 # over-fetch by one to determine whether there's a next page From 85a11eb39f8124ba5c3225649fbe5b0656695070 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Fri, 1 Nov 2024 09:00:15 -0700 Subject: [PATCH 2/9] add unit tests --- tests/unit/server/api/types/test_Project.py | 62 ++++++++++++++++++- .../server/api/types/test_ProjectSession.py | 2 - 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/tests/unit/server/api/types/test_Project.py b/tests/unit/server/api/types/test_Project.py index aa1c28f862..1bcaf27bc3 100644 --- a/tests/unit/server/api/types/test_Project.py +++ b/tests/unit/server/api/types/test_Project.py @@ -1,7 +1,7 @@ # ruff: noqa: E501 from datetime import datetime -from typing import Any +from typing import Any, NamedTuple import httpx import pytest @@ -11,8 +11,12 @@ from phoenix.config import DEFAULT_PROJECT_NAME from phoenix.db import models from phoenix.server.api.types.pagination import Cursor, CursorSortColumn, CursorSortColumnDataType +from phoenix.server.api.types.Project import Project +from phoenix.server.api.types.ProjectSession import ProjectSession from phoenix.server.types import DbSessionFactory +from ...._helpers import _add_project, _add_project_session, _add_span, _add_trace, _node + PROJECT_ID = str(GlobalID(type_name="Project", node_id="1")) @@ -1139,3 +1143,59 @@ async def llama_index_rag_spans(db: DbSessionFactory) -> None: }, ], ) + + +class _Data(NamedTuple): + spans: list[models.Span] + traces: list[models.Trace] + project_sessions: list[models.ProjectSession] + projects: list[models.Project] + + +class TestProject: + @staticmethod + async def _node( + field: str, + project: models.Project, + httpx_client: httpx.AsyncClient, + ) -> Any: + return await _node(field, Project.__name__, project.id, httpx_client) + + @pytest.fixture + async def _data( + self, + db: DbSessionFactory, + ) -> _Data: + projects = [] + project_sessions = [] + traces = [] + spans = [] + async with db() as session: + projects.append(await _add_project(session)) + project_sessions.append(await _add_project_session(session, projects[-1])) + traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) + attributes = {"input": {"value": "abc"}, "output": {"value": "cba"}} + spans.append(await _add_span(session, traces[-1], attributes=attributes)) + project_sessions.append(await _add_project_session(session, projects[-1])) + traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) + attributes = {"input": {"value": "xyz"}, "output": {"value": "zyx"}} + spans.append(await _add_span(session, traces[-1], attributes=attributes)) + return _Data( + spans=spans, + traces=traces, + project_sessions=project_sessions, + projects=projects, + ) + + async def test_sessions_filter_condition( + self, + _data: _Data, + httpx_client: httpx.AsyncClient, + ) -> None: + project = _data.projects[0] + project_session = _data.project_sessions[1] + id_ = str(GlobalID(ProjectSession.__name__, str(project_session.id))) + field = 'sessions(filterCondition:"' + "'y' in input.value" + '"){edges{node{id}}}' + assert await self._node(field, project, httpx_client) == { + "edges": [{"node": {"id": id_}}], + } diff --git a/tests/unit/server/api/types/test_ProjectSession.py b/tests/unit/server/api/types/test_ProjectSession.py index 1a0d69fbeb..4e4439cecd 100644 --- a/tests/unit/server/api/types/test_ProjectSession.py +++ b/tests/unit/server/api/types/test_ProjectSession.py @@ -3,7 +3,6 @@ import httpx import pytest -from faker import Faker from strawberry.relay import GlobalID from phoenix.db import models @@ -39,7 +38,6 @@ async def _node( async def _data( self, db: DbSessionFactory, - fake: Faker, ) -> _Data: project_sessions = [] traces = [] From f6a31cc18d231ae9bf05996b35d09a6d87107d92 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 09:42:45 -0800 Subject: [PATCH 3/9] search for substring --- app/schema.graphql | 2 +- app/src/pages/project/ProjectPage.tsx | 5 +- .../pages/project/SessionSubstringContext.tsx | 40 +++++++ .../pages/project/SessionSubstringField.tsx | 103 ++++++++++++++++++ app/src/pages/project/SessionsTable.tsx | 15 +-- .../ProjectPageSessionsQuery.graphql.ts | 4 +- .../SessionsTableQuery.graphql.ts | 34 +++--- .../SessionsTable_sessions.graphql.ts | 16 +-- .../fixtures/multi-turn_chat_sessions.ipynb | 22 +--- src/phoenix/server/api/types/Project.py | 40 ++++--- tests/unit/_helpers.py | 15 ++- tests/unit/server/api/types/test_Project.py | 44 +++++--- 12 files changed, 254 insertions(+), 86 deletions(-) create mode 100644 app/src/pages/project/SessionSubstringContext.tsx create mode 100644 app/src/pages/project/SessionSubstringField.tsx diff --git a/app/schema.graphql b/app/schema.graphql index a48ef934f7..3f2016a067 100644 --- a/app/schema.graphql +++ b/app/schema.graphql @@ -1220,7 +1220,7 @@ type Project implements Node { spanLatencyMsQuantile(probability: Float!, timeRange: TimeRange, filterCondition: String): Float trace(traceId: ID!): Trace spans(timeRange: TimeRange, first: Int = 50, last: Int, after: String, before: String, sort: SpanSort, rootSpansOnly: Boolean, filterCondition: String): SpanConnection! - sessions(timeRange: TimeRange, first: Int = 50, after: String, filterCondition: String): ProjectSessionConnection! + sessions(timeRange: TimeRange, first: Int = 50, after: String, substring: String): ProjectSessionConnection! """ Names of all available annotations for traces. (The list contains no duplicates.) diff --git a/app/src/pages/project/ProjectPage.tsx b/app/src/pages/project/ProjectPage.tsx index 5017fcf47b..d962f42a8d 100644 --- a/app/src/pages/project/ProjectPage.tsx +++ b/app/src/pages/project/ProjectPage.tsx @@ -23,6 +23,7 @@ import { ProjectPageSessionsQuery as ProjectPageSessionsQueryType } from "./__ge import { ProjectPageSpansQuery as ProjectPageSpansQueryType } from "./__generated__/ProjectPageSpansQuery.graphql"; import { ProjectPageHeader } from "./ProjectPageHeader"; import { SessionsTable } from "./SessionsTable"; +import { SessionSubstringProvider } from "./SessionSubstringContext"; import { SpanFilterConditionProvider } from "./SpanFilterConditionContext"; import { SpansTable } from "./SpansTable"; import { StreamToggle } from "./StreamToggle"; @@ -193,13 +194,13 @@ export function ProjectPageContent({ return ( isSelected && sessionsQueryReference && ( - + - + ) ); }} diff --git a/app/src/pages/project/SessionSubstringContext.tsx b/app/src/pages/project/SessionSubstringContext.tsx new file mode 100644 index 0000000000..b2ef04cc25 --- /dev/null +++ b/app/src/pages/project/SessionSubstringContext.tsx @@ -0,0 +1,40 @@ +import React, { + createContext, + PropsWithChildren, + startTransition, + useCallback, + useContext, + useState, +} from "react"; + +export type SessionSubstringContextType = { + substring: string; + setSubstring: (condition: string) => void; +}; + +export const SessionSubstringContext = + createContext(null); + +export function useSessionSubstring() { + const context = useContext(SessionSubstringContext); + if (context === null) { + throw new Error( + "useSessionSubstring must be used within a SessionSubstringProvider" + ); + } + return context; +} + +export function SessionSubstringProvider(props: PropsWithChildren) { + const [substring, _setSubstring] = useState(""); + const setSubstring = useCallback((condition: string) => { + startTransition(() => { + _setSubstring(condition); + }); + }, []); + return ( + + {props.children} + + ); +} diff --git a/app/src/pages/project/SessionSubstringField.tsx b/app/src/pages/project/SessionSubstringField.tsx new file mode 100644 index 0000000000..ac4b9c3546 --- /dev/null +++ b/app/src/pages/project/SessionSubstringField.tsx @@ -0,0 +1,103 @@ +import React, { useState } from "react"; +import { nord } from "@uiw/codemirror-theme-nord"; +import CodeMirror from "@uiw/react-codemirror"; +import { css } from "@emotion/react"; + +import { AddonBefore, Flex, Icon, Icons } from "@arizeai/components"; + +import { useTheme } from "../../contexts"; + +import { useSessionSubstring } from "./SessionSubstringContext"; + +const codeMirrorCSS = css` + flex: 1 1 auto; + .cm-content { + padding: var(--ac-global-dimension-static-size-100) 0; + } + .cm-editor { + background-color: transparent; + } + .cm-focused { + outline: none; + } + .cm-selectionLayer .cm-selectionBackground { + background: var(--ac-global-color-cyan-400) !important; + } +`; + +const fieldCSS = css` + border-width: var(--ac-global-border-size-thin); + border-style: solid; + border-color: var(--ac-global-input-field-border-color); + border-radius: var(--ac-global-rounding-small); + background-color: var(--ac-global-input-field-background-color); + transition: all 0.2s ease-in-out; + overflow-x: hidden; + &:hover, + &[data-is-focused="true"] { + border-color: var(--ac-global-input-field-border-color-active); + background-color: var(--ac-global-input-field-background-color-active); + } + &[data-is-invalid="true"] { + border-color: var(--ac-global-color-danger); + } + box-sizing: border-box; +`; + +type SessionsSubstringFieldProps = { + placeholder?: string; +}; +export function SessionSubstringField(props: SessionsSubstringFieldProps) { + const { placeholder = "search for substring" } = props; + const [isFocused, setIsFocused] = useState(false); + const { substring, setSubstring } = useSessionSubstring(); + const { theme } = useTheme(); + const codeMirrorTheme = theme === "dark" ? nord : undefined; + + const hasSubstring = substring !== ""; + return ( +
+ + + } /> + + setIsFocused(true)} + onBlur={() => setIsFocused(false)} + value={substring} + onChange={setSubstring} + height="36px" + width="100%" + theme={codeMirrorTheme} + placeholder={placeholder} + /> + + +
+ ); +} diff --git a/app/src/pages/project/SessionsTable.tsx b/app/src/pages/project/SessionsTable.tsx index 83f946330e..85de40caf0 100644 --- a/app/src/pages/project/SessionsTable.tsx +++ b/app/src/pages/project/SessionsTable.tsx @@ -33,7 +33,8 @@ import { TokenCount } from "../../components/trace/TokenCount"; import { SessionsTable_sessions$key } from "./__generated__/SessionsTable_sessions.graphql"; import { SessionsTableQuery } from "./__generated__/SessionsTableQuery.graphql"; import { SessionsTableEmpty } from "./SessionsTableEmpty"; -import { SpanFilterConditionField } from "./SpanFilterConditionField"; +import { useSessionSubstring } from "./SessionSubstringContext"; +import { SessionSubstringField } from "./SessionSubstringField"; import { spansTableCSS } from "./styles"; type SessionsTableProps = { @@ -46,7 +47,7 @@ export function SessionsTable(props: SessionsTableProps) { // we need a reference to the scrolling element for pagination logic down below const tableContainerRef = useRef(null); const [sorting, setSorting] = useState([]); - const [filterCondition, setFilterCondition] = useState(""); + const { substring } = useSessionSubstring(); const navigate = useNavigate(); const { fetchKey } = useStreamState(); const { data, loadNext, hasNext, isLoadingNext, refetch } = @@ -57,13 +58,13 @@ export function SessionsTable(props: SessionsTableProps) { @argumentDefinitions( after: { type: "String", defaultValue: null } first: { type: "Int", defaultValue: 50 } - filterCondition: { type: "String", defaultValue: null } + substring: { type: "String", defaultValue: null } ) { name sessions( first: $first after: $after - filterCondition: $filterCondition + substring: $substring timeRange: $timeRange ) @connection(key: "SessionsTable_sessions") { edges { @@ -159,12 +160,12 @@ export function SessionsTable(props: SessionsTableProps) { { after: null, first: PAGE_SIZE, - filterCondition: filterCondition, + substring: substring, }, { fetchPolicy: "store-and-network" } ); }); - }, [sorting, refetch, filterCondition, fetchKey]); + }, [sorting, refetch, substring, fetchKey]); const fetchMoreOnBottomReached = React.useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { @@ -213,7 +214,7 @@ export function SessionsTable(props: SessionsTableProps) { borderBottomWidth="thin" flex="none" > - +
> + * @generated SignedSource<<440f68eab401f0ac7aee511a5502478d>> * @lightSyntaxTransform * @nogrep */ @@ -302,7 +302,7 @@ return { "alias": null, "args": (v4/*: any*/), "filters": [ - "filterCondition", + "substring", "timeRange" ], "handle": "connection", diff --git a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts index 3af8b9b8fc..63fb26328a 100644 --- a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -16,9 +16,9 @@ export type TimeRange = { }; export type SessionsTableQuery$variables = { after?: string | null; - filterCondition?: string | null; first?: number | null; id: string; + substring?: string | null; timeRange?: TimeRange | null; }; export type SessionsTableQuery$data = { @@ -38,19 +38,19 @@ var v0 = { "name": "after" }, v1 = { - "defaultValue": null, + "defaultValue": 50, "kind": "LocalArgument", - "name": "filterCondition" + "name": "first" }, v2 = { - "defaultValue": 50, + "defaultValue": null, "kind": "LocalArgument", - "name": "first" + "name": "id" }, v3 = { "defaultValue": null, "kind": "LocalArgument", - "name": "id" + "name": "substring" }, v4 = { "defaultValue": null, @@ -71,13 +71,13 @@ v6 = { }, v7 = { "kind": "Variable", - "name": "filterCondition", - "variableName": "filterCondition" + "name": "first", + "variableName": "first" }, v8 = { "kind": "Variable", - "name": "first", - "variableName": "first" + "name": "substring", + "variableName": "substring" }, v9 = { "alias": null, @@ -154,9 +154,9 @@ return { "argumentDefinitions": [ (v0/*: any*/), (v1/*: any*/), - (v2/*: any*/), + (v3/*: any*/), (v4/*: any*/), - (v3/*: any*/) + (v2/*: any*/) ], "kind": "Operation", "name": "SessionsTableQuery", @@ -347,7 +347,7 @@ return { "alias": null, "args": (v11/*: any*/), "filters": [ - "filterCondition", + "substring", "timeRange" ], "handle": "connection", @@ -365,16 +365,16 @@ return { ] }, "params": { - "cacheID": "d7dd13061330467ffb806c85ab235a27", + "cacheID": "f0722a2fa5eef04b63512aff4913a115", "id": null, "metadata": {}, "name": "SessionsTableQuery", "operationKind": "query", - "text": "query SessionsTableQuery(\n $after: String = null\n $filterCondition: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_37AmCq\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_37AmCq on Project {\n name\n sessions(first: $first, after: $after, filterCondition: $filterCondition, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" + "text": "query SessionsTableQuery(\n $after: String = null\n $first: Int = 50\n $substring: String = null\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_2VwEYG\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_2VwEYG on Project {\n name\n sessions(first: $first, after: $after, substring: $substring, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "27bb1448e2935d7844a0a83e28f828c9"; +(node as any).hash = "e48d793bebd23a60a4164fcba2332371"; export default node; diff --git a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts index 40dafa4f56..7fbce93246 100644 --- a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<9136b4549ff709dd0d60a353b0c1f804>> + * @generated SignedSource<<806702249ca9cd8c677f73d36d919bd6>> * @lightSyntaxTransform * @nogrep */ @@ -72,14 +72,14 @@ return { "name": "after" }, { - "defaultValue": null, + "defaultValue": 50, "kind": "LocalArgument", - "name": "filterCondition" + "name": "first" }, { - "defaultValue": 50, + "defaultValue": null, "kind": "LocalArgument", - "name": "first" + "name": "substring" }, { "kind": "RootArgument", @@ -129,8 +129,8 @@ return { "args": [ { "kind": "Variable", - "name": "filterCondition", - "variableName": "filterCondition" + "name": "substring", + "variableName": "substring" }, { "kind": "Variable", @@ -306,6 +306,6 @@ return { }; })(); -(node as any).hash = "27bb1448e2935d7844a0a83e28f828c9"; +(node as any).hash = "e48d793bebd23a60a4164fcba2332371"; export default node; diff --git a/scripts/fixtures/multi-turn_chat_sessions.ipynb b/scripts/fixtures/multi-turn_chat_sessions.ipynb index 24f2f3a30a..26d70575d2 100644 --- a/scripts/fixtures/multi-turn_chat_sessions.ipynb +++ b/scripts/fixtures/multi-turn_chat_sessions.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "52b788701385cc6f", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "d87e76da34fee68b", "metadata": {}, "outputs": [], @@ -92,7 +92,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "4f60b2ad", "metadata": {}, "outputs": [], @@ -259,22 +259,8 @@ } ], "metadata": { - "kernelspec": { - "display_name": "phoenix", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" + "name": "python" } }, "nbformat": 4, diff --git a/src/phoenix/server/api/types/Project.py b/src/phoenix/server/api/types/Project.py index 4a866580bb..454f3a2dfd 100644 --- a/src/phoenix/server/api/types/Project.py +++ b/src/phoenix/server/api/types/Project.py @@ -1,14 +1,11 @@ import operator from datetime import datetime -from typing import ( - Any, - ClassVar, - Optional, -) +from typing import Any, ClassVar, Optional import strawberry from aioitertools.itertools import islice -from sqlalchemy import and_, desc, distinct, select +from openinference.semconv.trace import SpanAttributes +from sqlalchemy import and_, desc, distinct, or_, select from sqlalchemy.orm import contains_eager from sqlalchemy.sql.expression import tuple_ from strawberry import ID, UNSET @@ -261,7 +258,7 @@ async def sessions( time_range: Optional[TimeRange] = UNSET, first: Optional[int] = 50, after: Optional[CursorString] = UNSET, - filter_condition: Optional[str] = UNSET, + substring: Optional[str] = UNSET, ) -> Connection[ProjectSession]: table = models.ProjectSession stmt = select(table).filter_by(project_id=self.id_attr) @@ -273,14 +270,23 @@ async def sessions( if after: cursor = Cursor.from_string(after) stmt = stmt.where(table.id < cursor.rowid) - if filter_condition: - span_filter = SpanFilter(condition=filter_condition) - subq = span_filter( - ( - stmt.with_only_columns(distinct(table.id).label("id")) - .join_from(table, models.Trace) - .join_from(models.Trace, models.Span) - .where(models.Span.parent_id.is_(None)) + if substring: + subq = ( + stmt.with_only_columns(distinct(table.id).label("id")) + .join_from(table, models.Trace) + .join_from(models.Trace, models.Span) + .where(models.Span.parent_id.is_(None)) + .where( + or_( + models.TextContains( + models.Span.attributes[INPUT_VALUE].as_string(), + substring, + ), + models.TextContains( + models.Span.attributes[OUTPUT_VALUE].as_string(), + substring, + ), + ) ) ).subquery() stmt = stmt.join(subq, table.id == subq.c.id) @@ -428,3 +434,7 @@ def to_gql_project(project: models.Project) -> Project: gradient_start_color=project.gradient_start_color, gradient_end_color=project.gradient_end_color, ) + + +INPUT_VALUE = SpanAttributes.INPUT_VALUE.split(".") +OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE.split(".") diff --git a/tests/unit/_helpers.py b/tests/unit/_helpers.py index a0172263c3..b428821867 100644 --- a/tests/unit/_helpers.py +++ b/tests/unit/_helpers.py @@ -75,11 +75,11 @@ async def _add_trace( async def _add_span( session: AsyncSession, - trace: models.Trace, + trace: Optional[models.Trace] = None, + parent_span: Optional[models.Span] = None, attributes: Optional[Dict[str, Any]] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, - parent_span: Optional[models.Span] = None, span_kind: str = "LLM", cumulative_error_count: int = 0, cumulative_llm_token_count_prompt: int = 0, @@ -87,6 +87,15 @@ async def _add_span( ) -> models.Span: start_time = start_time or datetime.now(timezone.utc) end_time = end_time or (start_time + timedelta(seconds=10)) + if trace is None and parent_span is None: + project = await _add_project(session) + trace = await _add_trace(session, project) + if parent_span is not None: + trace_rowid = parent_span.trace_rowid + elif trace is not None: + trace_rowid = trace.id + else: + raise ValueError("Either `trace` or `parent_span` must be provided") span = models.Span( name=token_hex(4), span_id=token_hex(8), @@ -100,7 +109,7 @@ async def _add_span( cumulative_llm_token_count_prompt=cumulative_llm_token_count_prompt, cumulative_llm_token_count_completion=cumulative_llm_token_count_completion, attributes=attributes or {}, - trace_rowid=trace.id, + trace_rowid=trace_rowid, ) session.add(span) await session.flush() diff --git a/tests/unit/server/api/types/test_Project.py b/tests/unit/server/api/types/test_Project.py index 1bcaf27bc3..864f655ec9 100644 --- a/tests/unit/server/api/types/test_Project.py +++ b/tests/unit/server/api/types/test_Project.py @@ -1166,19 +1166,25 @@ async def _data( self, db: DbSessionFactory, ) -> _Data: - projects = [] - project_sessions = [] - traces = [] - spans = [] + projects, project_sessions, traces, spans = [], [], [], [] async with db() as session: projects.append(await _add_project(session)) + + project_sessions.append(await _add_project_session(session, projects[-1])) + traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) + attributes = {"input": {"value": "a\"'b"}, "output": {"value": "c\"'d"}} + spans.append(await _add_span(session, traces[-1], attributes=attributes)) + project_sessions.append(await _add_project_session(session, projects[-1])) traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) - attributes = {"input": {"value": "abc"}, "output": {"value": "cba"}} + attributes = {"input": {"value": "e\"'f"}, "output": {"value": "g\"'h"}} spans.append(await _add_span(session, traces[-1], attributes=attributes)) + attributes = {"input": {"value": "i\"'j"}, "output": {"value": "k\"'l"}} + spans.append(await _add_span(session, parent_span=spans[-1], attributes=attributes)) + project_sessions.append(await _add_project_session(session, projects[-1])) traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) - attributes = {"input": {"value": "xyz"}, "output": {"value": "zyx"}} + attributes = {"input": {"value": "g\"'h"}, "output": {"value": "e\"'f"}} spans.append(await _add_span(session, traces[-1], attributes=attributes)) return _Data( spans=spans, @@ -1187,15 +1193,27 @@ async def _data( projects=projects, ) - async def test_sessions_filter_condition( + async def test_sessions_substring_search_looks_at_both_input_and_output( self, _data: _Data, httpx_client: httpx.AsyncClient, ) -> None: project = _data.projects[0] - project_session = _data.project_sessions[1] - id_ = str(GlobalID(ProjectSession.__name__, str(project_session.id))) - field = 'sessions(filterCondition:"' + "'y' in input.value" + '"){edges{node{id}}}' - assert await self._node(field, project, httpx_client) == { - "edges": [{"node": {"id": id_}}], - } + field = 'sessions(substring:"\\"\'f"){edges{node{id}}}' + res = await self._node(field, project, httpx_client) + assert sorted(e["node"]["id"] for e in res["edges"]) == sorted( + [ + str(GlobalID(ProjectSession.__name__, str(_data.project_sessions[1].id))), + str(GlobalID(ProjectSession.__name__, str(_data.project_sessions[2].id))), + ] + ) + + async def test_sessions_substring_search_looks_at_only_root_spans( + self, + _data: _Data, + httpx_client: httpx.AsyncClient, + ) -> None: + project = _data.projects[0] + field = 'sessions(substring:"\\"\'j"){edges{node{id}}}' + res = await self._node(field, project, httpx_client) + assert {e["node"]["id"] for e in res["edges"]} == set() From 5f4c839683d1f284c6baaaa2806906d441897aff Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 09:51:44 -0800 Subject: [PATCH 4/9] rename field --- app/src/pages/project/ProjectPage.tsx | 2 +- ...bstringContext.tsx => SessionSearchContext.tsx} | 14 +++++++------- ...onSubstringField.tsx => SessionSearchField.tsx} | 6 +++--- app/src/pages/project/SessionsTable.tsx | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) rename app/src/pages/project/{SessionSubstringContext.tsx => SessionSearchContext.tsx} (65%) rename app/src/pages/project/{SessionSubstringField.tsx => SessionSearchField.tsx} (93%) diff --git a/app/src/pages/project/ProjectPage.tsx b/app/src/pages/project/ProjectPage.tsx index d962f42a8d..8cd00a7246 100644 --- a/app/src/pages/project/ProjectPage.tsx +++ b/app/src/pages/project/ProjectPage.tsx @@ -22,8 +22,8 @@ import { ProjectPageQuery } from "./__generated__/ProjectPageQuery.graphql"; import { ProjectPageSessionsQuery as ProjectPageSessionsQueryType } from "./__generated__/ProjectPageSessionsQuery.graphql"; import { ProjectPageSpansQuery as ProjectPageSpansQueryType } from "./__generated__/ProjectPageSpansQuery.graphql"; import { ProjectPageHeader } from "./ProjectPageHeader"; +import { SessionSubstringProvider } from "./SessionSearchContext"; import { SessionsTable } from "./SessionsTable"; -import { SessionSubstringProvider } from "./SessionSubstringContext"; import { SpanFilterConditionProvider } from "./SpanFilterConditionContext"; import { SpansTable } from "./SpansTable"; import { StreamToggle } from "./StreamToggle"; diff --git a/app/src/pages/project/SessionSubstringContext.tsx b/app/src/pages/project/SessionSearchContext.tsx similarity index 65% rename from app/src/pages/project/SessionSubstringContext.tsx rename to app/src/pages/project/SessionSearchContext.tsx index b2ef04cc25..be53f409c6 100644 --- a/app/src/pages/project/SessionSubstringContext.tsx +++ b/app/src/pages/project/SessionSearchContext.tsx @@ -7,16 +7,16 @@ import React, { useState, } from "react"; -export type SessionSubstringContextType = { +export type SessionSearchContextType = { substring: string; setSubstring: (condition: string) => void; }; -export const SessionSubstringContext = - createContext(null); +export const SessionSearchContext = + createContext(null); -export function useSessionSubstring() { - const context = useContext(SessionSubstringContext); +export function useSessionSearchContext() { + const context = useContext(SessionSearchContext); if (context === null) { throw new Error( "useSessionSubstring must be used within a SessionSubstringProvider" @@ -33,8 +33,8 @@ export function SessionSubstringProvider(props: PropsWithChildren) { }); }, []); return ( - + {props.children} - + ); } diff --git a/app/src/pages/project/SessionSubstringField.tsx b/app/src/pages/project/SessionSearchField.tsx similarity index 93% rename from app/src/pages/project/SessionSubstringField.tsx rename to app/src/pages/project/SessionSearchField.tsx index ac4b9c3546..d249d1464d 100644 --- a/app/src/pages/project/SessionSubstringField.tsx +++ b/app/src/pages/project/SessionSearchField.tsx @@ -7,7 +7,7 @@ import { AddonBefore, Flex, Icon, Icons } from "@arizeai/components"; import { useTheme } from "../../contexts"; -import { useSessionSubstring } from "./SessionSubstringContext"; +import { useSessionSearchContext } from "./SessionSearchContext"; const codeMirrorCSS = css` flex: 1 1 auto; @@ -47,10 +47,10 @@ const fieldCSS = css` type SessionsSubstringFieldProps = { placeholder?: string; }; -export function SessionSubstringField(props: SessionsSubstringFieldProps) { +export function SessionSearchField(props: SessionsSubstringFieldProps) { const { placeholder = "search for substring" } = props; const [isFocused, setIsFocused] = useState(false); - const { substring, setSubstring } = useSessionSubstring(); + const { substring, setSubstring } = useSessionSearchContext(); const { theme } = useTheme(); const codeMirrorTheme = theme === "dark" ? nord : undefined; diff --git a/app/src/pages/project/SessionsTable.tsx b/app/src/pages/project/SessionsTable.tsx index 85de40caf0..a0e64ed473 100644 --- a/app/src/pages/project/SessionsTable.tsx +++ b/app/src/pages/project/SessionsTable.tsx @@ -32,9 +32,9 @@ import { TokenCount } from "../../components/trace/TokenCount"; import { SessionsTable_sessions$key } from "./__generated__/SessionsTable_sessions.graphql"; import { SessionsTableQuery } from "./__generated__/SessionsTableQuery.graphql"; +import { useSessionSearchContext } from "./SessionSearchContext"; +import { SessionSearchField } from "./SessionSearchField"; import { SessionsTableEmpty } from "./SessionsTableEmpty"; -import { useSessionSubstring } from "./SessionSubstringContext"; -import { SessionSubstringField } from "./SessionSubstringField"; import { spansTableCSS } from "./styles"; type SessionsTableProps = { @@ -47,7 +47,7 @@ export function SessionsTable(props: SessionsTableProps) { // we need a reference to the scrolling element for pagination logic down below const tableContainerRef = useRef(null); const [sorting, setSorting] = useState([]); - const { substring } = useSessionSubstring(); + const { substring } = useSessionSearchContext(); const navigate = useNavigate(); const { fetchKey } = useStreamState(); const { data, loadNext, hasNext, isLoadingNext, refetch } = @@ -214,7 +214,7 @@ export function SessionsTable(props: SessionsTableProps) { borderBottomWidth="thin" flex="none" > - +
Date: Mon, 4 Nov 2024 09:56:45 -0800 Subject: [PATCH 5/9] rename as filterSubstring --- app/schema.graphql | 2 +- .../pages/project/SessionSearchContext.tsx | 8 +++-- app/src/pages/project/SessionSearchField.tsx | 10 +++--- app/src/pages/project/SessionsTable.tsx | 10 +++--- .../ProjectPageSessionsQuery.graphql.ts | 4 +-- .../SessionsTableQuery.graphql.ts | 34 +++++++++---------- .../SessionsTable_sessions.graphql.ts | 16 ++++----- src/phoenix/server/api/types/Project.py | 8 ++--- 8 files changed, 47 insertions(+), 45 deletions(-) diff --git a/app/schema.graphql b/app/schema.graphql index 3f2016a067..2ec253cd5b 100644 --- a/app/schema.graphql +++ b/app/schema.graphql @@ -1220,7 +1220,7 @@ type Project implements Node { spanLatencyMsQuantile(probability: Float!, timeRange: TimeRange, filterCondition: String): Float trace(traceId: ID!): Trace spans(timeRange: TimeRange, first: Int = 50, last: Int, after: String, before: String, sort: SpanSort, rootSpansOnly: Boolean, filterCondition: String): SpanConnection! - sessions(timeRange: TimeRange, first: Int = 50, after: String, substring: String): ProjectSessionConnection! + sessions(timeRange: TimeRange, first: Int = 50, after: String, filterSubstring: String): ProjectSessionConnection! """ Names of all available annotations for traces. (The list contains no duplicates.) diff --git a/app/src/pages/project/SessionSearchContext.tsx b/app/src/pages/project/SessionSearchContext.tsx index be53f409c6..a9f96177c6 100644 --- a/app/src/pages/project/SessionSearchContext.tsx +++ b/app/src/pages/project/SessionSearchContext.tsx @@ -8,8 +8,8 @@ import React, { } from "react"; export type SessionSearchContextType = { - substring: string; - setSubstring: (condition: string) => void; + filterSubstring: string; + setFilterSubstring: (condition: string) => void; }; export const SessionSearchContext = @@ -33,7 +33,9 @@ export function SessionSubstringProvider(props: PropsWithChildren) { }); }, []); return ( - + {props.children} ); diff --git a/app/src/pages/project/SessionSearchField.tsx b/app/src/pages/project/SessionSearchField.tsx index d249d1464d..5ca12a506e 100644 --- a/app/src/pages/project/SessionSearchField.tsx +++ b/app/src/pages/project/SessionSearchField.tsx @@ -50,11 +50,11 @@ type SessionsSubstringFieldProps = { export function SessionSearchField(props: SessionsSubstringFieldProps) { const { placeholder = "search for substring" } = props; const [isFocused, setIsFocused] = useState(false); - const { substring, setSubstring } = useSessionSearchContext(); + const { filterSubstring, setFilterSubstring } = useSessionSearchContext(); const { theme } = useTheme(); const codeMirrorTheme = theme === "dark" ? nord : undefined; - const hasSubstring = substring !== ""; + const hasSubstring = filterSubstring !== ""; return (
setIsFocused(true)} onBlur={() => setIsFocused(false)} - value={substring} - onChange={setSubstring} + value={filterSubstring} + onChange={setFilterSubstring} height="36px" width="100%" theme={codeMirrorTheme} @@ -92,7 +92,7 @@ export function SessionSearchField(props: SessionsSubstringFieldProps) { color: var(--ac-global-text-color-700); visibility: ${hasSubstring ? "visible" : "hidden"}; `} - onClick={() => setSubstring("")} + onClick={() => setFilterSubstring("")} className="button--reset" > } /> diff --git a/app/src/pages/project/SessionsTable.tsx b/app/src/pages/project/SessionsTable.tsx index a0e64ed473..74abebf54e 100644 --- a/app/src/pages/project/SessionsTable.tsx +++ b/app/src/pages/project/SessionsTable.tsx @@ -47,7 +47,7 @@ export function SessionsTable(props: SessionsTableProps) { // we need a reference to the scrolling element for pagination logic down below const tableContainerRef = useRef(null); const [sorting, setSorting] = useState([]); - const { substring } = useSessionSearchContext(); + const { filterSubstring } = useSessionSearchContext(); const navigate = useNavigate(); const { fetchKey } = useStreamState(); const { data, loadNext, hasNext, isLoadingNext, refetch } = @@ -58,13 +58,13 @@ export function SessionsTable(props: SessionsTableProps) { @argumentDefinitions( after: { type: "String", defaultValue: null } first: { type: "Int", defaultValue: 50 } - substring: { type: "String", defaultValue: null } + filterSubstring: { type: "String", defaultValue: null } ) { name sessions( first: $first after: $after - substring: $substring + filterSubstring: $filterSubstring timeRange: $timeRange ) @connection(key: "SessionsTable_sessions") { edges { @@ -160,12 +160,12 @@ export function SessionsTable(props: SessionsTableProps) { { after: null, first: PAGE_SIZE, - substring: substring, + filterSubstring: filterSubstring, }, { fetchPolicy: "store-and-network" } ); }); - }, [sorting, refetch, substring, fetchKey]); + }, [sorting, refetch, filterSubstring, fetchKey]); const fetchMoreOnBottomReached = React.useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { diff --git a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts index 6eef8af07a..67af0dc8f0 100644 --- a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts +++ b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<440f68eab401f0ac7aee511a5502478d>> + * @generated SignedSource<<411d8078c184ae892746699214e68531>> * @lightSyntaxTransform * @nogrep */ @@ -302,7 +302,7 @@ return { "alias": null, "args": (v4/*: any*/), "filters": [ - "substring", + "filterSubstring", "timeRange" ], "handle": "connection", diff --git a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts index 63fb26328a..a709a1d813 100644 --- a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<25ce553ce71e60c53a2bce7b45c3c7aa>> * @lightSyntaxTransform * @nogrep */ @@ -16,9 +16,9 @@ export type TimeRange = { }; export type SessionsTableQuery$variables = { after?: string | null; + filterSubstring?: string | null; first?: number | null; id: string; - substring?: string | null; timeRange?: TimeRange | null; }; export type SessionsTableQuery$data = { @@ -38,19 +38,19 @@ var v0 = { "name": "after" }, v1 = { - "defaultValue": 50, + "defaultValue": null, "kind": "LocalArgument", - "name": "first" + "name": "filterSubstring" }, v2 = { - "defaultValue": null, + "defaultValue": 50, "kind": "LocalArgument", - "name": "id" + "name": "first" }, v3 = { "defaultValue": null, "kind": "LocalArgument", - "name": "substring" + "name": "id" }, v4 = { "defaultValue": null, @@ -71,13 +71,13 @@ v6 = { }, v7 = { "kind": "Variable", - "name": "first", - "variableName": "first" + "name": "filterSubstring", + "variableName": "filterSubstring" }, v8 = { "kind": "Variable", - "name": "substring", - "variableName": "substring" + "name": "first", + "variableName": "first" }, v9 = { "alias": null, @@ -154,9 +154,9 @@ return { "argumentDefinitions": [ (v0/*: any*/), (v1/*: any*/), - (v3/*: any*/), + (v2/*: any*/), (v4/*: any*/), - (v2/*: any*/) + (v3/*: any*/) ], "kind": "Operation", "name": "SessionsTableQuery", @@ -347,7 +347,7 @@ return { "alias": null, "args": (v11/*: any*/), "filters": [ - "substring", + "filterSubstring", "timeRange" ], "handle": "connection", @@ -365,16 +365,16 @@ return { ] }, "params": { - "cacheID": "f0722a2fa5eef04b63512aff4913a115", + "cacheID": "52d61eee3ba1f879459e74cca7937847", "id": null, "metadata": {}, "name": "SessionsTableQuery", "operationKind": "query", - "text": "query SessionsTableQuery(\n $after: String = null\n $first: Int = 50\n $substring: String = null\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_2VwEYG\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_2VwEYG on Project {\n name\n sessions(first: $first, after: $after, substring: $substring, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" + "text": "query SessionsTableQuery(\n $after: String = null\n $filterSubstring: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_1Cj2jo\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_1Cj2jo on Project {\n name\n sessions(first: $first, after: $after, filterSubstring: $filterSubstring, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "e48d793bebd23a60a4164fcba2332371"; +(node as any).hash = "705198922586184108088980f9d9e9f1"; export default node; diff --git a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts index 7fbce93246..406d88b83c 100644 --- a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<806702249ca9cd8c677f73d36d919bd6>> + * @generated SignedSource<<6c34c6c321529716675a98bea6710e30>> * @lightSyntaxTransform * @nogrep */ @@ -72,14 +72,14 @@ return { "name": "after" }, { - "defaultValue": 50, + "defaultValue": null, "kind": "LocalArgument", - "name": "first" + "name": "filterSubstring" }, { - "defaultValue": null, + "defaultValue": 50, "kind": "LocalArgument", - "name": "substring" + "name": "first" }, { "kind": "RootArgument", @@ -129,8 +129,8 @@ return { "args": [ { "kind": "Variable", - "name": "substring", - "variableName": "substring" + "name": "filterSubstring", + "variableName": "filterSubstring" }, { "kind": "Variable", @@ -306,6 +306,6 @@ return { }; })(); -(node as any).hash = "e48d793bebd23a60a4164fcba2332371"; +(node as any).hash = "705198922586184108088980f9d9e9f1"; export default node; diff --git a/src/phoenix/server/api/types/Project.py b/src/phoenix/server/api/types/Project.py index 454f3a2dfd..329f181752 100644 --- a/src/phoenix/server/api/types/Project.py +++ b/src/phoenix/server/api/types/Project.py @@ -258,7 +258,7 @@ async def sessions( time_range: Optional[TimeRange] = UNSET, first: Optional[int] = 50, after: Optional[CursorString] = UNSET, - substring: Optional[str] = UNSET, + filter_substring: Optional[str] = UNSET, ) -> Connection[ProjectSession]: table = models.ProjectSession stmt = select(table).filter_by(project_id=self.id_attr) @@ -270,7 +270,7 @@ async def sessions( if after: cursor = Cursor.from_string(after) stmt = stmt.where(table.id < cursor.rowid) - if substring: + if filter_substring: subq = ( stmt.with_only_columns(distinct(table.id).label("id")) .join_from(table, models.Trace) @@ -280,11 +280,11 @@ async def sessions( or_( models.TextContains( models.Span.attributes[INPUT_VALUE].as_string(), - substring, + filter_substring, ), models.TextContains( models.Span.attributes[OUTPUT_VALUE].as_string(), - substring, + filter_substring, ), ) ) From 80a049db50d862607655dd5a0bf03c09de689a84 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 09:57:52 -0800 Subject: [PATCH 6/9] change placeholder --- app/src/pages/project/SessionSearchField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/pages/project/SessionSearchField.tsx b/app/src/pages/project/SessionSearchField.tsx index 5ca12a506e..c7ed2d2294 100644 --- a/app/src/pages/project/SessionSearchField.tsx +++ b/app/src/pages/project/SessionSearchField.tsx @@ -48,7 +48,7 @@ type SessionsSubstringFieldProps = { placeholder?: string; }; export function SessionSearchField(props: SessionsSubstringFieldProps) { - const { placeholder = "search for substring" } = props; + const { placeholder = "Search messages" } = props; const [isFocused, setIsFocused] = useState(false); const { filterSubstring, setFilterSubstring } = useSessionSearchContext(); const { theme } = useTheme(); From 9050e99c44433e540cf4ba4799f541e8dc56f0b5 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 10:21:09 -0800 Subject: [PATCH 7/9] update unit tests --- src/phoenix/server/api/schema.py | 3 +-- tests/unit/server/api/types/test_Project.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/phoenix/server/api/schema.py b/src/phoenix/server/api/schema.py index 0f153ba7cd..9dfeee734f 100644 --- a/src/phoenix/server/api/schema.py +++ b/src/phoenix/server/api/schema.py @@ -1,6 +1,5 @@ import strawberry -from phoenix.server.api.exceptions import get_mask_errors_extension from phoenix.server.api.mutations import Mutation from phoenix.server.api.queries import Query from phoenix.server.api.subscriptions import Subscription @@ -12,6 +11,6 @@ schema = strawberry.Schema( query=Query, mutation=Mutation, - extensions=[get_mask_errors_extension()], + # extensions=[get_mask_errors_extension()], subscription=Subscription, ) diff --git a/tests/unit/server/api/types/test_Project.py b/tests/unit/server/api/types/test_Project.py index 864f655ec9..b8e3ee9e0f 100644 --- a/tests/unit/server/api/types/test_Project.py +++ b/tests/unit/server/api/types/test_Project.py @@ -1186,6 +1186,7 @@ async def _data( traces.append(await _add_trace(session, projects[-1], project_sessions[-1])) attributes = {"input": {"value": "g\"'h"}, "output": {"value": "e\"'f"}} spans.append(await _add_span(session, traces[-1], attributes=attributes)) + spans.append(await _add_span(session, traces[-1])) return _Data( spans=spans, traces=traces, @@ -1199,7 +1200,7 @@ async def test_sessions_substring_search_looks_at_both_input_and_output( httpx_client: httpx.AsyncClient, ) -> None: project = _data.projects[0] - field = 'sessions(substring:"\\"\'f"){edges{node{id}}}' + field = 'sessions(filterSubstring:"\\"\'f"){edges{node{id}}}' res = await self._node(field, project, httpx_client) assert sorted(e["node"]["id"] for e in res["edges"]) == sorted( [ @@ -1214,6 +1215,6 @@ async def test_sessions_substring_search_looks_at_only_root_spans( httpx_client: httpx.AsyncClient, ) -> None: project = _data.projects[0] - field = 'sessions(substring:"\\"\'j"){edges{node{id}}}' + field = 'sessions(filterSubstring:"\\"\'j"){edges{node{id}}}' res = await self._node(field, project, httpx_client) assert {e["node"]["id"] for e in res["edges"]} == set() From 054eefd745bc4d98cba721a8ccd5f2aac357a7ce Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 10:34:22 -0800 Subject: [PATCH 8/9] clean up --- app/schema.graphql | 2 +- app/src/pages/project/ProjectPage.tsx | 6 +++--- app/src/pages/project/SessionSearchContext.tsx | 11 +++++++---- app/src/pages/project/SessionSearchField.tsx | 10 +++++----- app/src/pages/project/SessionsTable.tsx | 10 +++++----- .../ProjectPageSessionsQuery.graphql.ts | 4 ++-- .../SessionsTableQuery.graphql.ts | 18 +++++++++--------- .../SessionsTable_sessions.graphql.ts | 10 +++++----- src/phoenix/server/api/types/Project.py | 8 ++++---- tests/unit/server/api/types/test_Project.py | 4 ++-- 10 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/schema.graphql b/app/schema.graphql index 2ec253cd5b..db4c84989c 100644 --- a/app/schema.graphql +++ b/app/schema.graphql @@ -1220,7 +1220,7 @@ type Project implements Node { spanLatencyMsQuantile(probability: Float!, timeRange: TimeRange, filterCondition: String): Float trace(traceId: ID!): Trace spans(timeRange: TimeRange, first: Int = 50, last: Int, after: String, before: String, sort: SpanSort, rootSpansOnly: Boolean, filterCondition: String): SpanConnection! - sessions(timeRange: TimeRange, first: Int = 50, after: String, filterSubstring: String): ProjectSessionConnection! + sessions(timeRange: TimeRange, first: Int = 50, after: String, filterIoSubstring: String): ProjectSessionConnection! """ Names of all available annotations for traces. (The list contains no duplicates.) diff --git a/app/src/pages/project/ProjectPage.tsx b/app/src/pages/project/ProjectPage.tsx index 8cd00a7246..7623d97ca4 100644 --- a/app/src/pages/project/ProjectPage.tsx +++ b/app/src/pages/project/ProjectPage.tsx @@ -22,7 +22,7 @@ import { ProjectPageQuery } from "./__generated__/ProjectPageQuery.graphql"; import { ProjectPageSessionsQuery as ProjectPageSessionsQueryType } from "./__generated__/ProjectPageSessionsQuery.graphql"; import { ProjectPageSpansQuery as ProjectPageSpansQueryType } from "./__generated__/ProjectPageSpansQuery.graphql"; import { ProjectPageHeader } from "./ProjectPageHeader"; -import { SessionSubstringProvider } from "./SessionSearchContext"; +import { SessionSearchProvider } from "./SessionSearchContext"; import { SessionsTable } from "./SessionsTable"; import { SpanFilterConditionProvider } from "./SpanFilterConditionContext"; import { SpansTable } from "./SpansTable"; @@ -194,13 +194,13 @@ export function ProjectPageContent({ return ( isSelected && sessionsQueryReference && ( - + - + ) ); }} diff --git a/app/src/pages/project/SessionSearchContext.tsx b/app/src/pages/project/SessionSearchContext.tsx index a9f96177c6..e9c54edf2e 100644 --- a/app/src/pages/project/SessionSearchContext.tsx +++ b/app/src/pages/project/SessionSearchContext.tsx @@ -8,8 +8,8 @@ import React, { } from "react"; export type SessionSearchContextType = { - filterSubstring: string; - setFilterSubstring: (condition: string) => void; + filterIoSubstring: string; + setFilterIoSubstring: (condition: string) => void; }; export const SessionSearchContext = @@ -25,7 +25,7 @@ export function useSessionSearchContext() { return context; } -export function SessionSubstringProvider(props: PropsWithChildren) { +export function SessionSearchProvider(props: PropsWithChildren) { const [substring, _setSubstring] = useState(""); const setSubstring = useCallback((condition: string) => { startTransition(() => { @@ -34,7 +34,10 @@ export function SessionSubstringProvider(props: PropsWithChildren) { }, []); return ( {props.children} diff --git a/app/src/pages/project/SessionSearchField.tsx b/app/src/pages/project/SessionSearchField.tsx index c7ed2d2294..d5aee15e7d 100644 --- a/app/src/pages/project/SessionSearchField.tsx +++ b/app/src/pages/project/SessionSearchField.tsx @@ -50,11 +50,11 @@ type SessionsSubstringFieldProps = { export function SessionSearchField(props: SessionsSubstringFieldProps) { const { placeholder = "Search messages" } = props; const [isFocused, setIsFocused] = useState(false); - const { filterSubstring, setFilterSubstring } = useSessionSearchContext(); + const { filterIoSubstring, setFilterIoSubstring } = useSessionSearchContext(); const { theme } = useTheme(); const codeMirrorTheme = theme === "dark" ? nord : undefined; - const hasSubstring = filterSubstring !== ""; + const hasSubstring = filterIoSubstring !== ""; return (
setIsFocused(true)} onBlur={() => setIsFocused(false)} - value={filterSubstring} - onChange={setFilterSubstring} + value={filterIoSubstring} + onChange={setFilterIoSubstring} height="36px" width="100%" theme={codeMirrorTheme} @@ -92,7 +92,7 @@ export function SessionSearchField(props: SessionsSubstringFieldProps) { color: var(--ac-global-text-color-700); visibility: ${hasSubstring ? "visible" : "hidden"}; `} - onClick={() => setFilterSubstring("")} + onClick={() => setFilterIoSubstring("")} className="button--reset" > } /> diff --git a/app/src/pages/project/SessionsTable.tsx b/app/src/pages/project/SessionsTable.tsx index 74abebf54e..84ff188d7e 100644 --- a/app/src/pages/project/SessionsTable.tsx +++ b/app/src/pages/project/SessionsTable.tsx @@ -47,7 +47,7 @@ export function SessionsTable(props: SessionsTableProps) { // we need a reference to the scrolling element for pagination logic down below const tableContainerRef = useRef(null); const [sorting, setSorting] = useState([]); - const { filterSubstring } = useSessionSearchContext(); + const { filterIoSubstring } = useSessionSearchContext(); const navigate = useNavigate(); const { fetchKey } = useStreamState(); const { data, loadNext, hasNext, isLoadingNext, refetch } = @@ -58,13 +58,13 @@ export function SessionsTable(props: SessionsTableProps) { @argumentDefinitions( after: { type: "String", defaultValue: null } first: { type: "Int", defaultValue: 50 } - filterSubstring: { type: "String", defaultValue: null } + filterIoSubstring: { type: "String", defaultValue: null } ) { name sessions( first: $first after: $after - filterSubstring: $filterSubstring + filterIoSubstring: $filterIoSubstring timeRange: $timeRange ) @connection(key: "SessionsTable_sessions") { edges { @@ -160,12 +160,12 @@ export function SessionsTable(props: SessionsTableProps) { { after: null, first: PAGE_SIZE, - filterSubstring: filterSubstring, + filterIoSubstring: filterIoSubstring, }, { fetchPolicy: "store-and-network" } ); }); - }, [sorting, refetch, filterSubstring, fetchKey]); + }, [sorting, refetch, filterIoSubstring, fetchKey]); const fetchMoreOnBottomReached = React.useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { diff --git a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts index 67af0dc8f0..85a6332849 100644 --- a/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts +++ b/app/src/pages/project/__generated__/ProjectPageSessionsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<411d8078c184ae892746699214e68531>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -302,7 +302,7 @@ return { "alias": null, "args": (v4/*: any*/), "filters": [ - "filterSubstring", + "filterIoSubstring", "timeRange" ], "handle": "connection", diff --git a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts index a709a1d813..7e1e55f7be 100644 --- a/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTableQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<25ce553ce71e60c53a2bce7b45c3c7aa>> + * @generated SignedSource<<178451515ca7af64a4e6fbc1966e3b42>> * @lightSyntaxTransform * @nogrep */ @@ -16,7 +16,7 @@ export type TimeRange = { }; export type SessionsTableQuery$variables = { after?: string | null; - filterSubstring?: string | null; + filterIoSubstring?: string | null; first?: number | null; id: string; timeRange?: TimeRange | null; @@ -40,7 +40,7 @@ var v0 = { v1 = { "defaultValue": null, "kind": "LocalArgument", - "name": "filterSubstring" + "name": "filterIoSubstring" }, v2 = { "defaultValue": 50, @@ -71,8 +71,8 @@ v6 = { }, v7 = { "kind": "Variable", - "name": "filterSubstring", - "variableName": "filterSubstring" + "name": "filterIoSubstring", + "variableName": "filterIoSubstring" }, v8 = { "kind": "Variable", @@ -347,7 +347,7 @@ return { "alias": null, "args": (v11/*: any*/), "filters": [ - "filterSubstring", + "filterIoSubstring", "timeRange" ], "handle": "connection", @@ -365,16 +365,16 @@ return { ] }, "params": { - "cacheID": "52d61eee3ba1f879459e74cca7937847", + "cacheID": "4e6b96fbbfa7034e3501289566bc20d9", "id": null, "metadata": {}, "name": "SessionsTableQuery", "operationKind": "query", - "text": "query SessionsTableQuery(\n $after: String = null\n $filterSubstring: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_1Cj2jo\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_1Cj2jo on Project {\n name\n sessions(first: $first, after: $after, filterSubstring: $filterSubstring, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" + "text": "query SessionsTableQuery(\n $after: String = null\n $filterIoSubstring: String = null\n $first: Int = 50\n $timeRange: TimeRange\n $id: GlobalID!\n) {\n node(id: $id) {\n __typename\n ...SessionsTable_sessions_zrmri\n __isNode: __typename\n id\n }\n}\n\nfragment SessionsTable_sessions_zrmri on Project {\n name\n sessions(first: $first, after: $after, filterIoSubstring: $filterIoSubstring, timeRange: $timeRange) {\n edges {\n session: node {\n id\n sessionId\n numTraces\n startTime\n endTime\n firstInput {\n value\n }\n lastOutput {\n value\n }\n tokenUsage {\n prompt\n completion\n total\n }\n }\n cursor\n node {\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "705198922586184108088980f9d9e9f1"; +(node as any).hash = "e846bbacd897b12db7c1b5410055f394"; export default node; diff --git a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts index 406d88b83c..29140a837c 100644 --- a/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts +++ b/app/src/pages/project/__generated__/SessionsTable_sessions.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<6c34c6c321529716675a98bea6710e30>> + * @generated SignedSource<<46b70007fe439a545d01c7977762e568>> * @lightSyntaxTransform * @nogrep */ @@ -74,7 +74,7 @@ return { { "defaultValue": null, "kind": "LocalArgument", - "name": "filterSubstring" + "name": "filterIoSubstring" }, { "defaultValue": 50, @@ -129,8 +129,8 @@ return { "args": [ { "kind": "Variable", - "name": "filterSubstring", - "variableName": "filterSubstring" + "name": "filterIoSubstring", + "variableName": "filterIoSubstring" }, { "kind": "Variable", @@ -306,6 +306,6 @@ return { }; })(); -(node as any).hash = "705198922586184108088980f9d9e9f1"; +(node as any).hash = "e846bbacd897b12db7c1b5410055f394"; export default node; diff --git a/src/phoenix/server/api/types/Project.py b/src/phoenix/server/api/types/Project.py index 329f181752..6346acc1a6 100644 --- a/src/phoenix/server/api/types/Project.py +++ b/src/phoenix/server/api/types/Project.py @@ -258,7 +258,7 @@ async def sessions( time_range: Optional[TimeRange] = UNSET, first: Optional[int] = 50, after: Optional[CursorString] = UNSET, - filter_substring: Optional[str] = UNSET, + filter_io_substring: Optional[str] = UNSET, ) -> Connection[ProjectSession]: table = models.ProjectSession stmt = select(table).filter_by(project_id=self.id_attr) @@ -270,7 +270,7 @@ async def sessions( if after: cursor = Cursor.from_string(after) stmt = stmt.where(table.id < cursor.rowid) - if filter_substring: + if filter_io_substring: subq = ( stmt.with_only_columns(distinct(table.id).label("id")) .join_from(table, models.Trace) @@ -280,11 +280,11 @@ async def sessions( or_( models.TextContains( models.Span.attributes[INPUT_VALUE].as_string(), - filter_substring, + filter_io_substring, ), models.TextContains( models.Span.attributes[OUTPUT_VALUE].as_string(), - filter_substring, + filter_io_substring, ), ) ) diff --git a/tests/unit/server/api/types/test_Project.py b/tests/unit/server/api/types/test_Project.py index b8e3ee9e0f..dffafbc528 100644 --- a/tests/unit/server/api/types/test_Project.py +++ b/tests/unit/server/api/types/test_Project.py @@ -1200,7 +1200,7 @@ async def test_sessions_substring_search_looks_at_both_input_and_output( httpx_client: httpx.AsyncClient, ) -> None: project = _data.projects[0] - field = 'sessions(filterSubstring:"\\"\'f"){edges{node{id}}}' + field = 'sessions(filterIoSubstring:"\\"\'f"){edges{node{id}}}' res = await self._node(field, project, httpx_client) assert sorted(e["node"]["id"] for e in res["edges"]) == sorted( [ @@ -1215,6 +1215,6 @@ async def test_sessions_substring_search_looks_at_only_root_spans( httpx_client: httpx.AsyncClient, ) -> None: project = _data.projects[0] - field = 'sessions(filterSubstring:"\\"\'j"){edges{node{id}}}' + field = 'sessions(filterIoSubstring:"\\"\'j"){edges{node{id}}}' res = await self._node(field, project, httpx_client) assert {e["node"]["id"] for e in res["edges"]} == set() From b6256eb449e2774386c67e3af4faccb2dcf33df0 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 4 Nov 2024 10:36:13 -0800 Subject: [PATCH 9/9] clean up --- src/phoenix/server/api/schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/phoenix/server/api/schema.py b/src/phoenix/server/api/schema.py index 9dfeee734f..0f153ba7cd 100644 --- a/src/phoenix/server/api/schema.py +++ b/src/phoenix/server/api/schema.py @@ -1,5 +1,6 @@ import strawberry +from phoenix.server.api.exceptions import get_mask_errors_extension from phoenix.server.api.mutations import Mutation from phoenix.server.api.queries import Query from phoenix.server.api.subscriptions import Subscription @@ -11,6 +12,6 @@ schema = strawberry.Schema( query=Query, mutation=Mutation, - # extensions=[get_mask_errors_extension()], + extensions=[get_mask_errors_extension()], subscription=Subscription, )