From 7f3f495ecc1f9f3892ba658ebe091fcc1145c1c1 Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 11:40:59 -0700 Subject: [PATCH 1/9] initial tsquery refactor --- ell-studio/package-lock.json | 27 ++++++ ell-studio/package.json | 1 + ell-studio/src/App.js | 47 ++++++---- ell-studio/src/hooks/useBackend.js | 132 +++++++++++++++++++++++++++++ ell-studio/src/pages/Home.js | 40 +++------ ell-studio/src/pages/LMP.js | 91 +++++--------------- ell-studio/src/pages/Traces.js | 45 ++++------ ell-studio/src/utils/lmpUtils.js | 34 -------- 8 files changed, 241 insertions(+), 176 deletions(-) create mode 100644 ell-studio/src/hooks/useBackend.js diff --git a/ell-studio/package-lock.json b/ell-studio/package-lock.json index 4a183add..06c456c2 100644 --- a/ell-studio/package-lock.json +++ b/ell-studio/package-lock.json @@ -13,6 +13,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.51.21", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -4597,6 +4598,32 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz", + "integrity": "sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.21.tgz", + "integrity": "sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.51.21" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.2.tgz", diff --git a/ell-studio/package.json b/ell-studio/package.json index a3465957..877a97c4 100644 --- a/ell-studio/package.json +++ b/ell-studio/package.json @@ -8,6 +8,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.51.21", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/ell-studio/src/App.js b/ell-studio/src/App.js index 74732cac..083d4fb8 100644 --- a/ell-studio/src/App.js +++ b/ell-studio/src/App.js @@ -1,5 +1,6 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Sidebar from './components/Sidebar'; import Home from './pages/Home'; import LMP from './pages/LMP'; @@ -7,26 +8,38 @@ import Traces from './pages/Traces'; import { ThemeProvider } from './contexts/ThemeContext'; import './styles/globals.css'; import './styles/sourceCode.css'; -import refractor from 'refractor' + +// Create a client +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, // default: true + retry: false, // default: 3 + staleTime: 5 * 60 * 1000, // 5 minutes + }, + }, +}); function App() { return ( - - -
- -
-
- - } /> - } /> - } /> - -
+ + + +
+ +
+
+ + } /> + } /> + } /> + +
+
-
- - + + + ); } diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js new file mode 100644 index 00000000..ba123c9f --- /dev/null +++ b/ell-studio/src/hooks/useBackend.js @@ -0,0 +1,132 @@ +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; + + +const API_BASE_URL = "http://localhost:8080"; + +export const useLMPDetails = (name, id) => { + return useQuery({ + queryKey: ['lmpDetails', name, id], + queryFn: async () => { + const lmpResponse = await axios.get(`${API_BASE_URL}/api/lmps/${name}${id ? `/${id}` : ""}`); + const all_lmps_matching = lmpResponse.data; + return all_lmps_matching + .map((lmp) => ({ ...lmp, created_at: new Date(lmp.created_at) })) + .sort((a, b) => b.created_at - a.created_at)[0]; + } + }); +}; + +export const useVersionHistory = (name) => { + return useQuery({ + queryKey: ['versionHistory', name], + queryFn: async () => { + const response = await axios.get(`${API_BASE_URL}/api/lmps/${name}`); + return (response.data || []).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + } + }); +}; + +export const useInvocations = (name, id) => { + return useQuery({ + queryKey: ['invocations', name, id], + queryFn: async () => { + const response = await axios.get(`${API_BASE_URL}/api/invocations/${name}${id ? `/${id}` : ""}`); + return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + } + }); +}; + +export const useUses = (usesIds) => { + return useQuery({ + queryKey: ['uses', usesIds], + queryFn: async () => { + return Promise.all( + usesIds.map(async (use) => { + const useResponse = await axios.get(`${API_BASE_URL}/api/lmps/${use}`); + return useResponse.data[0]; + }) + ); + }, + enabled: !!usesIds && usesIds.length > 0, + }); +}; + +export const useAllInvocations = () => { + return useQuery({ + queryKey: ['allInvocations'], + queryFn: async () => { + const response = await axios.get(`${API_BASE_URL}/api/invocations`); + return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + } + }); +}; + + +export const useAllLMPs = () => { + return useQuery({ + queryKey: ['allLMPs'], + queryFn: async () => { + const response = await axios.get(`${API_BASE_URL}/api/lmps`); + const lmps = response.data; + + // Group LMPs by name + const lmpGroups = lmps.reduce((acc, lmp) => { + if (!acc[lmp.name]) { + acc[lmp.name] = []; + } + acc[lmp.name].push(lmp); + return acc; + }, {}); + + // Process each group + return Object.values(lmpGroups).map(group => { + const sortedVersions = group.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + const latestVersion = sortedVersions[0]; + + return { + ...latestVersion, + versions: sortedVersions.map(version => ({ + ...version, + created_at: new Date(version.created_at), + })), + }; + }); + } + }); +}; + + + const fetchTraces = async (lmps) => { + const baseUrl = API_BASE_URL + const response = await axios.get(`${baseUrl}/api/traces`); + // Filter out duplicate traces based on consumed and consumer + const uniqueTraces = response.data.reduce((acc, trace) => { + const key = `${trace.consumed}-${trace.consumer}`; + if (!acc[key]) { + acc[key] = trace; + } + return acc; + }, {}); + + // Convert the object of unique traces back to an array + const uniqueTracesArray = Object.values(uniqueTraces); + + // Filter out traces between LMPs that are not on the graph + const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); + const filteredTraces = uniqueTracesArray.filter(trace => + lmpIds.has(trace.consumed) && lmpIds.has(trace.consumer) + ); + + return filteredTraces; +} + + + +export const useTraces = (lmps) => { + return useQuery({ + queryKey: ['traces', lmps], + queryFn: () => fetchTraces(lmps), + enabled: !!lmps && lmps.length > 0, + }); + }; \ No newline at end of file diff --git a/ell-studio/src/pages/Home.js b/ell-studio/src/pages/Home.js index 40e5d19f..0a417d03 100644 --- a/ell-studio/src/pages/Home.js +++ b/ell-studio/src/pages/Home.js @@ -1,34 +1,17 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Link } from 'react-router-dom'; import { useTheme } from '../contexts/ThemeContext'; -import { fetchLMPs, getTimeAgo, fetchTraces } from '../utils/lmpUtils'; +import { getTimeAgo } from '../utils/lmpUtils'; import { DependencyGraph } from '../components/depgraph/DependencyGraph'; +import { useAllLMPs, useTraces } from '../hooks/useBackend'; function Home() { - const [lmps, setLmps] = useState([]); - const [loaded, setLoaded] = useState(false); - const { darkMode } = useTheme(); const [expandedLMP, setExpandedLMP] = useState(null); - const [traces, setTraces] = useState([]); - - useEffect(() => { - const getLMPs = async () => { - try { - const aggregatedLMPs = await fetchLMPs(); - const traces = await fetchTraces(aggregatedLMPs); - setLmps(aggregatedLMPs); - setTraces(traces); - - setLoaded(true); - } catch (error) { - console.error('Error fetching LMPs:', error); - } - }; - getLMPs(); - }, []); + const { darkMode } = useTheme(); + const { data: lmps, isLoading: isLoadingLMPs } = useAllLMPs(); + const { data: traces, isLoading: isLoadingTraces } = useTraces(lmps); const toggleExpand = (lmpName, event) => { - // Prevent toggling when clicking on the link if (event.target.tagName.toLowerCase() !== 'a') { setExpandedLMP(expandedLMP === lmpName ? null : lmpName); } @@ -38,11 +21,17 @@ function Home() { return id.length > 8 ? `${id.substring(0, 8)}...` : id; }; + if (isLoadingLMPs || isLoadingTraces) { + return
+

Loading...

+
; + } + return (

Language Model Programs

- {loaded && } + {lmps && traces && }
{lmps.map((lmp) => (
{ + if (!versionHistory) return null; + const currentVersionIndex = versionHistory.findIndex(v => v.lmp_id === lmp?.lmp_id); + return versionHistory.length > 1 && currentVersionIndex < versionHistory.length - 1 + ? versionHistory[currentVersionIndex + 1] + : null; + }, [versionHistory, lmp]); - useEffect(() => { - const fetchLMPDetails = async () => { - try { - const lmpResponse = await axios.get( - `${API_BASE_URL}/api/lmps/${name}${id ? `/${id}` : ""}` - ); - const all_lmps_matching = lmpResponse.data; - const latest_lmp = all_lmps_matching - .map((lmp) => ({ ...lmp, created_at: new Date(lmp.created_at) })) - .sort((a, b) => b.created_at - a.created_at)[0]; - setLmp(latest_lmp); - - const versionHistoryResponse = await axios.get( - `${API_BASE_URL}/api/lmps/${latest_lmp.name}` - ); - const sortedVersionHistory = (versionHistoryResponse.data || []).sort( - (a, b) => new Date(b.created_at) - new Date(a.created_at) - ); - setVersionHistory(sortedVersionHistory); - const currentVersionIndex = sortedVersionHistory.findIndex(v => v.lmp_id === latest_lmp.lmp_id); - const hasPreviousVersion = sortedVersionHistory.length > 1 && currentVersionIndex < sortedVersionHistory.length - 1; - setPreviousVersion(hasPreviousVersion ? sortedVersionHistory[currentVersionIndex + 1] : null); - - const invocationsResponse = await axios.get( - `${API_BASE_URL}/api/invocations/${name}${id ? `/${id}` : ""}` - ); - const sortedInvocations = invocationsResponse.data.sort( - (a, b) => b.created_at - a.created_at - ); - setInvocations(sortedInvocations); - - const usesIds = latest_lmp.uses; - console.log(usesIds); - const uses = await Promise.all( - usesIds.map(async (use) => { - const useResponse = await axios.get( - `${API_BASE_URL}/api/lmps/${use}` - ); - return useResponse.data[0]; - }) - ); - setUses(uses); - } catch (error) { - console.error("Error fetching LMP details:", error); - } - }; - fetchLMPDetails(); - }, [name, id, API_BASE_URL]); - - const requestedInvocation = useMemo(() => invocations.find( + const requestedInvocation = useMemo(() => invocations?.find( (invocation) => invocation.id === requestedInvocationId ), [invocations, requestedInvocationId]); @@ -108,7 +61,7 @@ function LMP() { }, [requestedInvocation]); const handleCopyCode = () => { - const fullCode = `${lmp.dependencies.trim()}\n\n${lmp.source.trim()}`; + const fullCode = `${lmp?.dependencies.trim()}\n\n${lmp?.source.trim()}`; navigator.clipboard .writeText(fullCode) .then(() => { @@ -130,16 +83,14 @@ function LMP() { setViewMode(prevMode => prevMode === 'Source' ? 'Diff' : 'Source'); }; - - - if (!lmp) + if (isLoadingLMP) return (
Loading...
); - const omitColumns = ['name']; // We want to omit the 'LMP' column on the LMP page + const omitColumns = ['name']; return (

@@ -179,8 +130,8 @@ function LMP() { • )} @@ -281,7 +232,7 @@ function LMP() { )} {activeTab === "dependency_graph" && ( - lmp.lmp_id).sort().join('-')} lmp={lmp} uses={uses} /> + lmp.lmp_id).sort().join('-')} lmp={lmp} uses={uses} /> )}

diff --git a/ell-studio/src/pages/Traces.js b/ell-studio/src/pages/Traces.js index 3988f5f6..3a0b1623 100644 --- a/ell-studio/src/pages/Traces.js +++ b/ell-studio/src/pages/Traces.js @@ -1,52 +1,39 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { FiCopy, FiZap, FiEdit2, FiFilter, FiClock, FiColumns, FiPause, FiPlay } from 'react-icons/fi'; import InvocationsTable from '../components/invocations/InvocationsTable'; -import axios from 'axios'; import InvocationsLayout from '../components/invocations/InvocationsLayout'; - import { useNavigate, useLocation } from 'react-router-dom'; +import { useAllInvocations } from '../hooks/useBackend'; - -const API_BASE_URL = "http://localhost:8080"; const Traces = () => { const [selectedTrace, setSelectedTrace] = useState(null); - const [invocations, setInvocations] = useState([]); const [isPolling, setIsPolling] = useState(true); const navigate = useNavigate(); const location = useLocation(); - const fetchInvocations = useCallback(async () => { - try { - const invocationsResponse = await axios.get(`${API_BASE_URL}/api/invocations`); - const sortedInvocations = invocationsResponse.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - setInvocations(sortedInvocations); - - // Check for invocation ID in URL - const searchParams = new URLSearchParams(location.search); - const invocationId = searchParams.get('i'); - if (invocationId) { - const selectedInvocation = sortedInvocations.find(inv => inv.id === invocationId); - if (selectedInvocation) { - setSelectedTrace(selectedInvocation); - } - } - } catch (error) { - console.error('Error fetching invocations:', error); - } - }, [location.search]); + const { data: invocations, refetch } = useAllInvocations(); useEffect(() => { - fetchInvocations(); // Initial fetch - let intervalId; if (isPolling) { - intervalId = setInterval(fetchInvocations, 200); // Poll every 200ms + intervalId = setInterval(refetch, 200); // Poll every 200ms } return () => { if (intervalId) clearInterval(intervalId); }; - }, [isPolling, fetchInvocations]); + }, [isPolling, refetch]); + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const invocationId = searchParams.get('i'); + if (invocationId && invocations) { + const selectedInvocation = invocations.find(inv => inv.id === invocationId); + if (selectedInvocation) { + setSelectedTrace(selectedInvocation); + } + } + }, [location.search, invocations]); const togglePolling = () => { setIsPolling(!isPolling); diff --git a/ell-studio/src/utils/lmpUtils.js b/ell-studio/src/utils/lmpUtils.js index 0ea77d55..2cec0dba 100644 --- a/ell-studio/src/utils/lmpUtils.js +++ b/ell-studio/src/utils/lmpUtils.js @@ -1,16 +1,5 @@ import axios from 'axios'; -const API_BASE_URL = "http://localhost:8080" -export const fetchLMPs = async () => { - try { - const baseUrl = API_BASE_URL - const response = await axios.get(`${baseUrl}/api/lmps`); - return aggregateLMPsByName(response.data); - } catch (error) { - console.error('Error fetching LMPs:', error); - throw error; - } -}; export const aggregateLMPsByName = (lmpList) => { const lmpsByName = new Map() @@ -48,29 +37,6 @@ export const aggregateLMPsByName = (lmpList) => { return latestLMPs }; -export const fetchTraces = async (lmps) => { - const baseUrl = API_BASE_URL - const response = await axios.get(`${baseUrl}/api/traces`); - // Filter out duplicate traces based on consumed and consumer - const uniqueTraces = response.data.reduce((acc, trace) => { - const key = `${trace.consumed}-${trace.consumer}`; - if (!acc[key]) { - acc[key] = trace; - } - return acc; - }, {}); - - // Convert the object of unique traces back to an array - const uniqueTracesArray = Object.values(uniqueTraces); - - // Filter out traces between LMPs that are not on the graph - const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); - const filteredTraces = uniqueTracesArray.filter(trace => - lmpIds.has(trace.consumed) && lmpIds.has(trace.consumer) - ); - - return filteredTraces; -} export function getTimeAgo(date) { From bece0ebfe23cdb6cabe6ce676659cf0eabdb1b7f Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 12:50:23 -0700 Subject: [PATCH 2/9] fixed invocation table queries.. --- .../src/components/HierarchicalTable.js | 65 +++++++++++++++++- .../invocations/InvocationsTable.js | 19 ++++- ell-studio/src/hooks/useBackend.js | 43 ++++++++---- ell-studio/src/pages/LMP.js | 6 ++ ell-studio/src/pages/Traces.js | 12 +++- ell-studio/src/utils/lmpUtils.js | 40 ----------- examples/chord_progression.mid | Bin 344 -> 415 bytes examples/hello_world.py | 18 +++-- examples/multilmp.py | 3 - src/ell/stores/sql.py | 39 +++++++++-- src/ell/studio/data_server.py | 42 +++++++---- src/ell/types.py | 12 ++-- 12 files changed, 211 insertions(+), 88 deletions(-) diff --git a/ell-studio/src/components/HierarchicalTable.js b/ell-studio/src/components/HierarchicalTable.js index 2c52430f..2be14b29 100644 --- a/ell-studio/src/components/HierarchicalTable.js +++ b/ell-studio/src/components/HierarchicalTable.js @@ -1,5 +1,5 @@ import React, { useMemo, useRef, useEffect, useState } from 'react'; -import { FiChevronRight, FiChevronDown, FiArrowUp, FiArrowDown } from 'react-icons/fi'; +import { FiChevronDown, FiArrowUp, FiArrowDown, FiChevronLeft, FiChevronRight, FiChevronsLeft, FiChevronsRight } from 'react-icons/fi'; import { HierarchicalTableProvider, useHierarchicalTable } from './HierarchicalTableContext'; import { Checkbox } from "components/common/Checkbox" @@ -124,7 +124,57 @@ const TableBody = ({ schema, onRowClick, columnWidths, updateWidth, rowClassName ); }; -const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initialSortConfig, rowClassName }) => { +const PaginationControls = ({ currentPage, totalPages, onPageChange, pageSize, totalItems }) => { + // const startItem = currentPage * pageSize + 1; + // const endItem = Math.min((currentPage + 1) * pageSize, totalItems); + + return ( +
+
+ {/* Showing {startItem} to {endItem} of {totalItems} items */} +
+
+ + + + Page {currentPage + 1} + + + +
+
+ ); +}; + +const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initialSortConfig, rowClassName, currentPage, onPageChange, pageSize, totalItems }) => { const [columnWidths, setColumnWidths] = useState({}); const updateWidth = (key, width, maxWidth) => { setColumnWidths(prev => ({ @@ -141,6 +191,8 @@ const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initia setColumnWidths(initialWidths); }, [schema]); + const totalPages = Math.ceil(totalItems / pageSize); + return (
+ {onPageChange && ( + + )} ); }; diff --git a/ell-studio/src/components/invocations/InvocationsTable.js b/ell-studio/src/components/invocations/InvocationsTable.js index 20c927e0..a7cee00d 100644 --- a/ell-studio/src/components/invocations/InvocationsTable.js +++ b/ell-studio/src/components/invocations/InvocationsTable.js @@ -1,20 +1,27 @@ import { LMPCardTitle } from '../depgraph/LMPCardTitle'; import HierarchicalTable from '../HierarchicalTable'; -import React, { useMemo, useCallback, useEffect } from 'react'; +import React, { useMemo, useCallback, useEffect, useState } from 'react'; import { Card } from '../Card'; import { getTimeAgo } from '../../utils/lmpUtils'; import VersionBadge from '../VersionBadge'; import { useNavigate } from 'react-router-dom'; import { lstrCleanStringify } from '../../utils/lstrCleanStringify'; +import { useInvocations } from '../../hooks/useBackend'; -const InvocationsTable = ({ invocations, onSelectTrace, currentlySelectedTrace, omitColumns = [] }) => { +const InvocationsTable = ({ invocations, currentPage, setCurrentPage, pageSize, onSelectTrace, currentlySelectedTrace, omitColumns = [] }) => { const navigate = useNavigate(); + + const onClickLMP = useCallback(({lmp, id : invocationId}) => { navigate(`/lmp/${lmp.name}/${lmp.lmp_id}?i=${invocationId}`); }, [navigate]); + const isLoading = !invocations; + + const traces = useMemo(() => { + if (!invocations) return []; return invocations.map(inv => ({ name: inv.lmp?.name || 'Unknown', input: lstrCleanStringify(inv.args.length === 1 ? inv.args[0] : inv.args), @@ -107,6 +114,10 @@ const InvocationsTable = ({ invocations, onSelectTrace, currentlySelectedTrace, const initialSortConfig = { key: 'created_at', direction: 'desc' }; + const hasNextPage = traces.length === pageSize; + + if (isLoading) return
Loading...
; + return ( item.id === currentlySelectedTrace?.id ? 'bg-blue-600 bg-opacity-30' : '' } + currentPage={currentPage} + onPageChange={setCurrentPage} + pageSize={pageSize} + hasNextPage={hasNextPage} /> ); }; diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index ba123c9f..73c463f1 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -17,21 +17,23 @@ export const useLMPDetails = (name, id) => { }); }; -export const useVersionHistory = (name) => { +export const useVersionHistory = (name, page = 0, pageSize = 100) => { return useQuery({ - queryKey: ['versionHistory', name], + queryKey: ['versionHistory', name, page, pageSize], queryFn: async () => { - const response = await axios.get(`${API_BASE_URL}/api/lmps/${name}`); + const skip = page * pageSize; + const response = await axios.get(`${API_BASE_URL}/api/lmps/${name}?skip=${skip}&limit=${pageSize}`); return (response.data || []).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } }); }; -export const useInvocations = (name, id) => { +export const useInvocations = (name, id, page = 0, pageSize = 100) => { return useQuery({ - queryKey: ['invocations', name, id], + queryKey: ['invocations', name, id, page, pageSize], queryFn: async () => { - const response = await axios.get(`${API_BASE_URL}/api/invocations/${name}${id ? `/${id}` : ""}`); + const skip = page * pageSize; + const response = await axios.get(`${API_BASE_URL}/api/invocations/${name ? name : ""}${name && id ? `/${id}` : ""}?skip=${skip}&limit=${pageSize}`); return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } }); @@ -52,22 +54,24 @@ export const useUses = (usesIds) => { }); }; -export const useAllInvocations = () => { +export const useAllInvocations = (page = 0, pageSize = 100) => { return useQuery({ - queryKey: ['allInvocations'], + queryKey: ['allInvocations', page, pageSize], queryFn: async () => { - const response = await axios.get(`${API_BASE_URL}/api/invocations`); + const skip = page * pageSize; + const response = await axios.get(`${API_BASE_URL}/api/invocations?skip=${skip}&limit=${pageSize}`); return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } }); }; -export const useAllLMPs = () => { +export const useAllLMPs = (page = 0, pageSize = 100) => { return useQuery({ - queryKey: ['allLMPs'], + queryKey: ['allLMPs', page, pageSize], queryFn: async () => { - const response = await axios.get(`${API_BASE_URL}/api/lmps`); + const skip = page * pageSize; + const response = await axios.get(`${API_BASE_URL}/api/lmps?skip=${skip}&limit=${pageSize}`); const lmps = response.data; // Group LMPs by name @@ -129,4 +133,17 @@ export const useTraces = (lmps) => { queryFn: () => fetchTraces(lmps), enabled: !!lmps && lmps.length > 0, }); - }; \ No newline at end of file + }; + +// New function for searching invocations +export const useSearchInvocations = (query, page = 0, pageSize = 100) => { + return useQuery({ + queryKey: ['searchInvocations', query, page, pageSize], + queryFn: async () => { + const skip = page * pageSize; + const response = await axios.post(`${API_BASE_URL}/api/invocations/search?q=${encodeURIComponent(query)}&skip=${skip}&limit=${pageSize}`); + return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + }, + enabled: !!query, + }); +}; \ No newline at end of file diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index 348f9cd5..19fe413b 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -36,6 +36,7 @@ function LMP() { const { data: lmp, isLoading: isLoadingLMP } = useLMPDetails(name, id); const { data: versionHistory } = useVersionHistory(name); + console.log(name,id) const { data: invocations } = useInvocations(name, id); const { data: uses } = useUses(lmp?.uses); @@ -59,6 +60,9 @@ function LMP() { useEffect(() => { setSelectedTrace(requestedInvocation); }, [requestedInvocation]); + + const [currentPage, setCurrentPage] = useState(0); + const pageSize = 10; const handleCopyCode = () => { const fullCode = `${lmp?.dependencies.trim()}\n\n${lmp?.source.trim()}`; @@ -218,6 +222,8 @@ function LMP() {
{ setSelectedTrace(trace); diff --git a/ell-studio/src/pages/Traces.js b/ell-studio/src/pages/Traces.js index 3a0b1623..cb9d2cc8 100644 --- a/ell-studio/src/pages/Traces.js +++ b/ell-studio/src/pages/Traces.js @@ -11,8 +11,11 @@ const Traces = () => { const navigate = useNavigate(); const location = useLocation(); - const { data: invocations, refetch } = useAllInvocations(); + const { data: invocations, refetch , isLoading } = useAllInvocations(); + const [currentPage, setCurrentPage] = useState(0); + const pageSize = 10; + useEffect(() => { let intervalId; if (isPolling) { @@ -44,6 +47,10 @@ const Traces = () => { navigate(`?i=${trace.id}`); }; + if (isLoading) { + return
Loading...
; + } + return ( {
diff --git a/ell-studio/src/utils/lmpUtils.js b/ell-studio/src/utils/lmpUtils.js index 2cec0dba..948e38bc 100644 --- a/ell-studio/src/utils/lmpUtils.js +++ b/ell-studio/src/utils/lmpUtils.js @@ -1,43 +1,3 @@ -import axios from 'axios'; - - -export const aggregateLMPsByName = (lmpList) => { - const lmpsByName = new Map() - const lmpNameById = new Map() - lmpList.forEach(lmp => { - const cleanedLmp = { - ...lmp, - created_at: new Date(lmp.created_at + 'Z'), - } - if (!lmpsByName.has(lmp.name)) { - lmpsByName.set(lmp.name, [cleanedLmp]); - } else { - lmpsByName.get(lmp.name).push(cleanedLmp); - } - lmpNameById.set(lmp.lmp_id, lmp.name) - }); - - const latestLMPByName = new Map() - lmpsByName.forEach((lmpVersions, lmpName) => { - const latestLMP = lmpVersions.sort((a, b) => b.created_at - a.created_at)[0] - latestLMPByName.set(lmpName, latestLMP) - }) - - // Now make a latest LMP by nam but add all the versions as a property on that lmp - const latestLMPs = Array.from(latestLMPByName.values()) - latestLMPs.forEach(lmp => { - lmp.versions = lmpsByName.get(lmp.name).sort((a, b) => b.created_at - a.created_at) - // sanetize the uses of the lmp to only have the latest version of the lmp whose id has that name - lmp.uses = lmp.uses.map(useId => { - const useLmpName = lmpNameById.get(useId) - return latestLMPByName.get(useLmpName).lmp_id - }) - }) - - return latestLMPs -}; - - export function getTimeAgo(date) { const now = new Date(); diff --git a/examples/chord_progression.mid b/examples/chord_progression.mid index 9bf8c074f0c0c2944d7e9ff007098592931b0780..b212761ee70b4d2e71b77054ebb4bcdddeaacac0 100644 GIT binary patch literal 415 zcmaiw!3u&<5Jkscv}qCjL4VL9iik=r5nTudeu9w^Bs`(@{6X)N{4=}bOKB0fxQln@ za^IavvLYv<2{|-Ub&}}^UQ=o=YIgFdS&~K0iyAdQ7p2Y@sV;B&T2=S*N%fe_AtmEO z0TiWgVgLqU0FD56&=C?-C^Xj_b1+#lvT;Zf66ij9Ah|iI9&e&?%#8o literal 344 zcmeYb$w*;fU|?flWME=G;2Tnu4dih%{10U2cXD9(ug?GxV$^5&&n)PikzbUe5Kxq# zUX)r~oSC1;aDatjf=vL!1P35?4(MlS0CF0DoCasG0K)`#AngT`017k!ISpPw0aqaQ v0Ae4I1d!7JlZ diff --git a/examples/hello_world.py b/examples/hello_world.py index 20118088..ab1ed1ec 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -4,22 +4,32 @@ from ell.stores.sql import SQLiteStore +class MyPrompt: + x : int + def get_random_length(): - return int(np.random.beta(2, 6) * 3000) + return int(np.random.beta(2, 6) * 1500) @ell.lm(model="gpt-4o-mini") def hello(world : str): - """Your goal is to charm the other guy""" # System prpmpt + """Your goal is to be really meant to the other guy whiel say hello""" name = world.capitalize() number_of_chars_in_name = get_random_length() - return f"Say hello to {name} in {number_of_chars_in_name*2} characters or more!" # User prompt + return f"Say hello to {name} in {number_of_chars_in_name} characters or more!" if __name__ == "__main__": ell.config.verbose = True ell.set_store(SQLiteStore('sqlite_example'), autocommit=True) - ell.set_default_lm_params(max_tokens=5) + greeting = hello("sam altman") # > "hello sama! ... " + + + # F_Theta: X -> Y + + # my_prompt_omega: Z -> X + + diff --git a/examples/multilmp.py b/examples/multilmp.py index e5229f4a..05d99fd3 100644 --- a/examples/multilmp.py +++ b/examples/multilmp.py @@ -45,6 +45,3 @@ def write_a_really_good_story(about : str): # with ell.cache(write_a_really_good_story): story = write_a_really_good_story("a dog") - - - ell.get_store() \ No newline at end of file diff --git a/src/ell/stores/sql.py b/src/ell/stores/sql.py index 77ae040b..3b4c6274 100644 --- a/src/ell/stores/sql.py +++ b/src/ell/stores/sql.py @@ -109,7 +109,8 @@ def write_invocation(self, id: str, lmp_id: str, args: str, kwargs: str, result: )) session.commit() - def get_lmps(self, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: + + def get_lmps(self, skip: int = 0, limit: int = 10, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: with Session(self.engine) as session: query = select(SerializedLMP, SerializedLMPUses.lmp_user_id).outerjoin( SerializedLMPUses, @@ -119,6 +120,8 @@ def get_lmps(self, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: if filters: for key, value in filters.items(): query = query.where(getattr(SerializedLMP, key) == value) + + query = query.offset(skip).limit(limit) results = session.exec(query).all() lmp_dict = {lmp.lmp_id: {**lmp.model_dump(), 'uses': []} for lmp, _ in results} @@ -127,7 +130,7 @@ def get_lmps(self, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: lmp_dict[lmp.lmp_id]['uses'].append(using_id) return list(lmp_dict.values()) - def get_invocations(self, lmp_filters: Dict[str, Any], filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]: + def get_invocations(self, lmp_filters: Dict[str, Any], skip: int = 0, limit: int = 10, filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]: with Session(self.engine) as session: query = select(Invocation, SerializedLStr, SerializedLMP).join(SerializedLMP).outerjoin(SerializedLStr) @@ -141,7 +144,7 @@ def get_invocations(self, lmp_filters: Dict[str, Any], filters: Optional[Dict[st query = query.where(getattr(Invocation, key) == value) # Sort from newest to oldest - query = query.order_by(Invocation.created_at.desc()) + query = query.order_by(Invocation.created_at.desc()).offset(skip).limit(limit) results = session.exec(query).all() @@ -206,7 +209,6 @@ def get_all_traces_leading_to(self, invocation_id: str) -> List[Dict[str, Any]]: .where(InvocationTrace.invocation_consumer_id == current_invocation_id) ).all() for row in results: - print(row) trace = { 'consumer_id': row.InvocationTrace.invocation_consumer_id, 'consumed': {key: value for key, value in row.Invocation.__dict__.items() if key not in ['invocation_consumer_id', 'invocation_consuming_id']}, @@ -214,7 +216,7 @@ def get_all_traces_leading_to(self, invocation_id: str) -> List[Dict[str, Any]]: } traces.append(trace) queue.append((row.Invocation.id, depth + 1)) - + # Create a dictionary to store unique traces based on consumed.id unique_traces = {} for trace in traces: @@ -232,6 +234,33 @@ def get_lmp_versions(self, name: str) -> List[Dict[str, Any]]: def get_latest_lmps(self) -> List[Dict[str, Any]]: raise NotImplementedError() + def search_invocations(self, q: str, skip: int = 0, limit: int = 10) -> List[Dict[str, Any]]: + with Session(self.engine) as session: + query = select(Invocation, SerializedLStr, SerializedLMP).join(SerializedLMP).outerjoin(SerializedLStr) + query = query.where( + or_( + Invocation.args.contains(q), + Invocation.kwargs.contains(q), + SerializedLStr.content.contains(q), + SerializedLMP.name.contains(q) + ) + ) + query = query.order_by(Invocation.created_at.desc()).offset(skip).limit(limit) + + results = session.exec(query).all() + + invocations = {} + for inv, lstr, lmp in results: + if inv.id not in invocations: + inv_dict = inv.model_dump() + inv_dict['lmp'] = lmp.model_dump() + invocations[inv.id] = inv_dict + invocations[inv.id]['results'] = [] + if lstr: + invocations[inv.id]['results'].append(dict(**lstr.model_dump(), __lstr=True)) + + return list(invocations.values()) + class SQLiteStore(SQLStore): def __init__(self, storage_dir: str): diff --git a/src/ell/studio/data_server.py b/src/ell/studio/data_server.py index d091b7ff..65e173ba 100644 --- a/src/ell/studio/data_server.py +++ b/src/ell/studio/data_server.py @@ -2,7 +2,7 @@ from typing import Optional, Dict, Any, List from ell.stores.sql import SQLiteStore from ell import __version__ -from fastapi import FastAPI, Query, HTTPException +from fastapi import FastAPI, Query, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware import os import logging @@ -27,17 +27,24 @@ def create_app(storage_dir: Optional[str] = None): ) @app.get("/api/lmps") - def get_lmps(): - lmps = serializer.get_lmps() + def get_lmps( + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) + ): + lmps = serializer.get_lmps(skip=skip, limit=limit) return lmps @app.get("/api/lmps/{name_or_id:path}") - def get_lmp(name_or_id: str): + def get_lmp( + name_or_id: str, + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) + ): # Remove any leading slash if present name_or_id = name_or_id.lstrip("/") # First, try to get by name - lmps_by_name = serializer.get_lmps(name=name_or_id) + lmps_by_name = serializer.get_lmps(name=name_or_id, skip=skip, limit=limit) if lmps_by_name: return list(lmps_by_name) @@ -51,7 +58,7 @@ def get_lmp(name_or_id: str): if len(name_parts) > 1: potential_lmp_id = name_parts[-1] potential_name = "/".join(name_parts[:-1]) - lmps = serializer.get_lmps(name=potential_name, lmp_id=potential_lmp_id) + lmps = serializer.get_lmps(name=potential_name, lmp_id=potential_lmp_id, skip=skip, limit=limit) if lmps: return list(lmps) @@ -59,7 +66,11 @@ def get_lmp(name_or_id: str): @app.get("/api/invocations") @app.get("/api/invocations/{name:path}") - def get_invocations(name: Optional[str] = None): + def get_invocations( + name: Optional[str] = None, + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) + ): lmp_filters = {} if name: name = name.lstrip("/") @@ -70,21 +81,28 @@ def get_invocations(name: Optional[str] = None): potential_lmp_id = name_parts[-1] lmp_filters["lmp_id"] = potential_lmp_id - invocations = serializer.get_invocations(lmp_filters=lmp_filters) + invocations = serializer.get_invocations(lmp_filters=lmp_filters, skip=skip, limit=limit) return invocations @app.post("/api/invocations/search") - def search_invocations(q: str = Query(...)): - invocations = serializer.search_invocations(q) + def search_invocations( + q: str = Query(...), + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) + ): + invocations = serializer.search_invocations(q, skip=skip, limit=limit) return invocations @app.get("/api/traces") - def get_consumption_graph(): + def get_consumption_graph( + ): traces = serializer.get_traces() return traces @app.get("/api/traces/{invocation_id}") - def get_all_traces_leading_to(invocation_id: str): + def get_all_traces_leading_to( + invocation_id: str, + ): traces = serializer.get_all_traces_leading_to(invocation_id) return traces diff --git a/src/ell/types.py b/src/ell/types.py index 233b3835..0103cccd 100644 --- a/src/ell/types.py +++ b/src/ell/types.py @@ -50,8 +50,8 @@ class SerializedLMPUses(SQLModel, table=True): This class is used to track which LMPs use or are used by other LMPs. """ - lmp_user_id: Optional[str] = Field(default=None, foreign_key="serializedlmp.lmp_id", primary_key=True) # ID of the LMP that is being used - lmp_using_id: Optional[str] = Field(default=None, foreign_key="serializedlmp.lmp_id", primary_key=True) # ID of the LMP that is using the other LMP + lmp_user_id: Optional[str] = Field(default=None, foreign_key="serializedlmp.lmp_id", primary_key=True, index=True) # ID of the LMP that is being used + lmp_using_id: Optional[str] = Field(default=None, foreign_key="serializedlmp.lmp_id", primary_key=True, index=True) # ID of the LMP that is using the other LMP @@ -108,8 +108,8 @@ class InvocationTrace(SQLModel, table=True): This class is used to keep track of when an invocation consumes a in its kwargs or args a result of another invocation. """ - invocation_consumer_id: str = Field(foreign_key="invocation.id", primary_key=True) # ID of the Invocation that is consuming another Invocation - invocation_consuming_id: str = Field(foreign_key="invocation.id", primary_key=True) # ID of the Invocation that is being consumed by another Invocation + invocation_consumer_id: str = Field(foreign_key="invocation.id", primary_key=True, index=True) # ID of the Invocation that is consuming another Invocation + invocation_consuming_id: str = Field(foreign_key="invocation.id", primary_key=True, index=True) # ID of the Invocation that is being consumed by another Invocation class Invocation(SQLModel, table=True): @@ -119,7 +119,7 @@ class Invocation(SQLModel, table=True): This class is used to store information about each time an LMP is called. """ id: Optional[str] = Field(default=None, primary_key=True) # Unique identifier for the invocation - lmp_id: str = Field(foreign_key="serializedlmp.lmp_id") # ID of the LMP that was invoked + lmp_id: str = Field(foreign_key="serializedlmp.lmp_id", index=True) # ID of the LMP that was invoked args: List[Any] = Field(default_factory=list, sa_column=Column(JSON)) # Arguments used in the invocation kwargs: dict = Field(default_factory=dict, sa_column=Column(JSON)) # Keyword arguments used in the invocation @@ -169,7 +169,7 @@ class SerializedLStr(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) # Unique identifier for the LStr content: str # The actual content of the LStr logits: List[float] = Field(default_factory=list, sa_column=Column(JSON)) # Logits associated with the LStr, if available - producer_invocation_id: Optional[int] = Field(default=None, foreign_key="invocation.id") # ID of the Invocation that produced this LStr + producer_invocation_id: Optional[int] = Field(default=None, foreign_key="invocation.id", index=True) # ID of the Invocation that produced this LStr producer_invocation: Optional[Invocation] = Relationship(back_populates="results") # Relationship to the Invocation that produced this LStr # Convert an SerializedLStr to an lstr From df01ad6b4f7d5e8236bf66691a992f808b84b942 Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 12:51:54 -0700 Subject: [PATCH 3/9] buttoning up uses --- ell-studio/src/pages/LMP.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index 19fe413b..f89c0f61 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -237,7 +237,7 @@ function LMP() { {activeTab === "version_history" && ( )} - {activeTab === "dependency_graph" && ( + {activeTab === "dependency_graph" && !!uses && ( lmp.lmp_id).sort().join('-')} lmp={lmp} uses={uses} /> )} From 73484f4794afc01156f90a36ff4337a5ac3e263b Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 12:57:34 -0700 Subject: [PATCH 4/9] removed verison request --- ell-studio/src/hooks/useBackend.js | 13 +------------ ell-studio/src/pages/LMP.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index 73c463f1..f6732414 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -12,18 +12,7 @@ export const useLMPDetails = (name, id) => { const all_lmps_matching = lmpResponse.data; return all_lmps_matching .map((lmp) => ({ ...lmp, created_at: new Date(lmp.created_at) })) - .sort((a, b) => b.created_at - a.created_at)[0]; - } - }); -}; - -export const useVersionHistory = (name, page = 0, pageSize = 100) => { - return useQuery({ - queryKey: ['versionHistory', name, page, pageSize], - queryFn: async () => { - const skip = page * pageSize; - const response = await axios.get(`${API_BASE_URL}/api/lmps/${name}?skip=${skip}&limit=${pageSize}`); - return (response.data || []).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + .sort((a, b) => b.created_at - a.created_at); } }); }; diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index f89c0f61..dbc3750a 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -34,8 +34,16 @@ function LMP() { let [searchParams, setSearchParams] = useSearchParams(); const requestedInvocationId = searchParams.get("i"); - const { data: lmp, isLoading: isLoadingLMP } = useLMPDetails(name, id); - const { data: versionHistory } = useVersionHistory(name); + const { data: versionHistory, isLoading: isLoadingLMP } = useLMPDetails(name); + const lmp = useMemo(() => { + if (!versionHistory) return null; + if (id) { + return versionHistory.find(v => v.lmp_id === id); + } else { + return versionHistory[0]; + } + }, [versionHistory, id]); + console.log(name,id) const { data: invocations } = useInvocations(name, id); const { data: uses } = useUses(lmp?.uses); From ad04f8733cbdd3d2a681c9dcd30fca6d7bf8db67 Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 13:18:49 -0700 Subject: [PATCH 5/9] latest lmps endpoint --- ell-studio/src/hooks/useBackend.js | 56 ++++++++++-------------------- ell-studio/src/pages/Home.js | 31 +++-------------- ell-studio/src/pages/LMP.js | 7 ++-- src/ell/store.py | 9 ----- src/ell/stores/sql.py | 34 +++++++++++++----- src/ell/studio/data_server.py | 13 ++++++- src/ell/types.py | 4 +-- 7 files changed, 66 insertions(+), 88 deletions(-) diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index f6732414..e0a96b17 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -1,10 +1,10 @@ -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueries } from '@tanstack/react-query'; import axios from 'axios'; const API_BASE_URL = "http://localhost:8080"; -export const useLMPDetails = (name, id) => { +export const useLMPs = (name, id) => { return useQuery({ queryKey: ['lmpDetails', name, id], queryFn: async () => { @@ -28,19 +28,20 @@ export const useInvocations = (name, id, page = 0, pageSize = 100) => { }); }; -export const useUses = (usesIds) => { - return useQuery({ - queryKey: ['uses', usesIds], - queryFn: async () => { - return Promise.all( - usesIds.map(async (use) => { - const useResponse = await axios.get(`${API_BASE_URL}/api/lmps/${use}`); - return useResponse.data[0]; - }) - ); - }, - enabled: !!usesIds && usesIds.length > 0, +export const useMultipleLMPs = (usesIds) => { + const multipleLMPs = useQueries({ + queries: (usesIds || []).map(use => ({ + queryKey: ['lmp', use], + queryFn: async () => { + const useResponse = await axios.get(`${API_BASE_URL}/api/lmps/${use}`); + return useResponse.data[0]; + }, + enabled: !!use, + })), }); + const isLoading = multipleLMPs.some(query => query.isLoading); + const data = multipleLMPs.map(query => query.data); + return { isLoading, data }; }; export const useAllInvocations = (page = 0, pageSize = 100) => { @@ -60,31 +61,10 @@ export const useAllLMPs = (page = 0, pageSize = 100) => { queryKey: ['allLMPs', page, pageSize], queryFn: async () => { const skip = page * pageSize; - const response = await axios.get(`${API_BASE_URL}/api/lmps?skip=${skip}&limit=${pageSize}`); + const response = await axios.get(`${API_BASE_URL}/api/latest/lmps?skip=${skip}&limit=${pageSize}`); const lmps = response.data; - - // Group LMPs by name - const lmpGroups = lmps.reduce((acc, lmp) => { - if (!acc[lmp.name]) { - acc[lmp.name] = []; - } - acc[lmp.name].push(lmp); - return acc; - }, {}); - - // Process each group - return Object.values(lmpGroups).map(group => { - const sortedVersions = group.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - const latestVersion = sortedVersions[0]; - - return { - ...latestVersion, - versions: sortedVersions.map(version => ({ - ...version, - created_at: new Date(version.created_at), - })), - }; - }); + + return lmps; } }); }; diff --git a/ell-studio/src/pages/Home.js b/ell-studio/src/pages/Home.js index 0a417d03..4e71efff 100644 --- a/ell-studio/src/pages/Home.js +++ b/ell-studio/src/pages/Home.js @@ -4,7 +4,7 @@ import { useTheme } from '../contexts/ThemeContext'; import { getTimeAgo } from '../utils/lmpUtils'; import { DependencyGraph } from '../components/depgraph/DependencyGraph'; import { useAllLMPs, useTraces } from '../hooks/useBackend'; - +import VersionBadge from '../components/VersionBadge'; function Home() { const [expandedLMP, setExpandedLMP] = useState(null); const { darkMode } = useTheme(); @@ -49,15 +49,13 @@ function Home() {
- ID: {truncateId(lmp.versions[0].lmp_id)} + ID: {truncateId(lmp.lmp_id)} Latest - - {lmp.versions.length} Version{lmp.versions.length > 1 ? 's' : ''} - +
@@ -65,28 +63,7 @@ function Home() { {lmp.source.length > 100 ? `${lmp.source.substring(0, 100)}...` : lmp.source}
-
-

- Last Updated: {getTimeAgo(lmp.versions[0].created_at)} -

-
- {expandedLMP === lmp.name && lmp.versions.length > 1 && ( -
-

Version History:

-
    - {lmp.versions.map((version, index) => ( -
  • -
    -
    -

    Version {lmp.versions.length - index}

    -

    {new Date(version.created_at * 1000).toLocaleString()}

    -

    Invocations: {version.invocations}

    -
    -
  • - ))} -
-
- )} + ))} diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index dbc3750a..445a7434 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useMemo } from "react"; import { useParams, useSearchParams, useNavigate, Link } from "react-router-dom"; -import { useLMPDetails, useVersionHistory, useInvocations, useUses } from "../hooks/useBackend"; +import { useLMPs, useInvocations, useMultipleLMPs } from "../hooks/useBackend"; import InvocationsTable from "../components/invocations/InvocationsTable"; import DependencyGraphPane from "../components/DependencyGraphPane"; import LMPSourceView from "../components/source/LMPSourceView"; @@ -34,7 +34,7 @@ function LMP() { let [searchParams, setSearchParams] = useSearchParams(); const requestedInvocationId = searchParams.get("i"); - const { data: versionHistory, isLoading: isLoadingLMP } = useLMPDetails(name); + const { data: versionHistory, isLoading: isLoadingLMP } = useLMPs(name); const lmp = useMemo(() => { if (!versionHistory) return null; if (id) { @@ -46,7 +46,8 @@ function LMP() { console.log(name,id) const { data: invocations } = useInvocations(name, id); - const { data: uses } = useUses(lmp?.uses); + const { data: uses } = useMultipleLMPs(lmp?.uses); + console.log(uses) const [activeTab, setActiveTab] = useState("runs"); const [selectedTrace, setSelectedTrace] = useState(null); diff --git a/src/ell/store.py b/src/ell/store.py index 97ede0cd..02a46ffc 100644 --- a/src/ell/store.py +++ b/src/ell/store.py @@ -97,15 +97,6 @@ def get_invocations(self, lmp_id: str, filters: Optional[Dict[str, Any]] = None) # """ # pass - @abstractmethod - def get_lmp_versions(self, lmp_id: str) -> List[Dict[str, Any]]: - """ - Retrieve all versions of an LMP from the storage. - - :param lmp_id: Unique identifier for the LMP. - :return: List of LMP versions. - """ - pass @abstractmethod def get_latest_lmps(self) -> List[Dict[str, Any]]: diff --git a/src/ell/stores/sql.py b/src/ell/stores/sql.py index 3b4c6274..fce6aa78 100644 --- a/src/ell/stores/sql.py +++ b/src/ell/stores/sql.py @@ -110,17 +110,41 @@ def write_invocation(self, id: str, lmp_id: str, args: str, kwargs: str, result: session.commit() - def get_lmps(self, skip: int = 0, limit: int = 10, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: + def get_latest_lmps(self, skip: int = 0, limit: int = 10) -> List[Dict[str, Any]]: + """ + Gets all the lmps grouped by unique name with the highest created at + """ + subquery = ( + select(SerializedLMP.name, func.max(SerializedLMP.created_at).label("max_created_at")) + .group_by(SerializedLMP.name) + .subquery() + ) + + filters = { + "name": subquery.c.name, + "created_at": subquery.c.max_created_at + } + + return self.get_lmps(skip=skip, limit=limit, subquery=subquery, **filters) + + def get_lmps(self, skip: int = 0, limit: int = 10, subquery=None, **filters: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]: with Session(self.engine) as session: query = select(SerializedLMP, SerializedLMPUses.lmp_user_id).outerjoin( SerializedLMPUses, SerializedLMP.lmp_id == SerializedLMPUses.lmp_using_id - ).order_by(SerializedLMP.created_at.desc()) # Sort by created_at in descending order + ) + + if subquery is not None: + query = query.join(subquery, and_( + SerializedLMP.name == subquery.c.name, + SerializedLMP.created_at == subquery.c.max_created_at + )) if filters: for key, value in filters.items(): query = query.where(getattr(SerializedLMP, key) == value) + query = query.order_by(SerializedLMP.created_at.desc()) # Sort by created_at in descending order query = query.offset(skip).limit(limit) results = session.exec(query).all() @@ -228,12 +252,6 @@ def get_all_traces_leading_to(self, invocation_id: str) -> List[Dict[str, Any]]: return list(unique_traces.values()) - def get_lmp_versions(self, name: str) -> List[Dict[str, Any]]: - return self.get_lmps(name=name) - - def get_latest_lmps(self) -> List[Dict[str, Any]]: - raise NotImplementedError() - def search_invocations(self, q: str, skip: int = 0, limit: int = 10) -> List[Dict[str, Any]]: with Session(self.engine) as session: query = select(Invocation, SerializedLStr, SerializedLMP).join(SerializedLMP).outerjoin(SerializedLStr) diff --git a/src/ell/studio/data_server.py b/src/ell/studio/data_server.py index 65e173ba..e04bb7f3 100644 --- a/src/ell/studio/data_server.py +++ b/src/ell/studio/data_server.py @@ -33,6 +33,16 @@ def get_lmps( ): lmps = serializer.get_lmps(skip=skip, limit=limit) return lmps + + @app.get("/api/latest/lmps") + def get_latest_lmps( + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100) + ): + lmps = serializer.get_latest_lmps( + skip=skip, limit=limit, + ) + return lmps @app.get("/api/lmps/{name_or_id:path}") def get_lmp( @@ -106,4 +116,5 @@ def get_all_traces_leading_to( traces = serializer.get_all_traces_leading_to(invocation_id) return traces - return app \ No newline at end of file + return app + diff --git a/src/ell/types.py b/src/ell/types.py index 0103cccd..ee5bb44d 100644 --- a/src/ell/types.py +++ b/src/ell/types.py @@ -62,10 +62,10 @@ class SerializedLMP(SQLModel, table=True): This class is used to store and retrieve LMP information in the database. """ lmp_id: Optional[str] = Field(default=None, primary_key=True) # Unique identifier for the LMP, now an index - name: str # Name of the LMP + name: str = Field(index=True) # Name of the LMP source: str # Source code or reference for the LMP dependencies: str # List of dependencies for the LMP, stored as a string - created_at: datetime = Field(default_factory=datetime.utcnow) # Timestamp of when the LMP was created + created_at: datetime = Field(default_factory=datetime.utcnow, index=True) # Timestamp of when the LMP was created is_lm: bool # Boolean indicating if it is an LM (Language Model) or an LMP lm_kwargs: dict = Field(sa_column=Column(JSON)) # Additional keyword arguments for the LMP From 38376eebc394ce7bd8d5197f2db1844be465db44 Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 13:27:01 -0700 Subject: [PATCH 6/9] render out of version nodes --- .../src/components/depgraph/LMPCardTitle.js | 2 +- .../src/components/depgraph/graphUtils.js | 18 +++++++++++++++++- ell-studio/src/hooks/useBackend.js | 2 +- ell-studio/src/pages/Home.js | 4 ++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ell-studio/src/components/depgraph/LMPCardTitle.js b/ell-studio/src/components/depgraph/LMPCardTitle.js index 1a4cb981..c747361a 100644 --- a/ell-studio/src/components/depgraph/LMPCardTitle.js +++ b/ell-studio/src/components/depgraph/LMPCardTitle.js @@ -29,7 +29,7 @@ export function LMPCardTitle({ {lmp.name}() - {displayVersion && } + {displayVersion && } ); } \ No newline at end of file diff --git a/ell-studio/src/components/depgraph/graphUtils.js b/ell-studio/src/components/depgraph/graphUtils.js index 49708a63..698aea66 100644 --- a/ell-studio/src/components/depgraph/graphUtils.js +++ b/ell-studio/src/components/depgraph/graphUtils.js @@ -190,8 +190,9 @@ export const useLayoutedElements = () => { return [true, { toggle, isRunning }]; }, [initialised]); }; - export function getInitialGraph(lmps, traces) { + const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); + const initialNodes = lmps .filter((x) => !!x) @@ -204,6 +205,21 @@ export function getInitialGraph(lmps, traces) { }; }) || []; + // Create dead nodes for missing LMPs + const deadNodes = lmps + .filter((x) => !!x) + .flatMap((lmp) => + (lmp.uses || []).filter(use => !lmpIds.has(use)).map(use => ({ + id: `${use}`, + type: "lmp", + data: { label: `Unknown LMP (${use})`, lmp: { lmp_id: use, name: `Out of Date LMP (${use})`, version_number: -2 } }, + position: { x: 0, y: 0 }, + style: { opacity: 0.5 }, // Make dead nodes visually distinct + })) + ); + + initialNodes.push(...deadNodes); + const initialEdges = lmps .filter((x) => !!x) diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index e0a96b17..edb5e3f6 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -56,7 +56,7 @@ export const useAllInvocations = (page = 0, pageSize = 100) => { }; -export const useAllLMPs = (page = 0, pageSize = 100) => { +export const useLatestLMPs = (page = 0, pageSize = 100) => { return useQuery({ queryKey: ['allLMPs', page, pageSize], queryFn: async () => { diff --git a/ell-studio/src/pages/Home.js b/ell-studio/src/pages/Home.js index 4e71efff..e1543b66 100644 --- a/ell-studio/src/pages/Home.js +++ b/ell-studio/src/pages/Home.js @@ -3,12 +3,12 @@ import { Link } from 'react-router-dom'; import { useTheme } from '../contexts/ThemeContext'; import { getTimeAgo } from '../utils/lmpUtils'; import { DependencyGraph } from '../components/depgraph/DependencyGraph'; -import { useAllLMPs, useTraces } from '../hooks/useBackend'; +import { useLatestLMPs, useTraces } from '../hooks/useBackend'; import VersionBadge from '../components/VersionBadge'; function Home() { const [expandedLMP, setExpandedLMP] = useState(null); const { darkMode } = useTheme(); - const { data: lmps, isLoading: isLoadingLMPs } = useAllLMPs(); + const { data: lmps, isLoading: isLoadingLMPs } = useLatestLMPs(); const { data: traces, isLoading: isLoadingTraces } = useTraces(lmps); const toggleExpand = (lmpName, event) => { From 2e723e57667bdb6d6ed12fe06d2419a32107f476 Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 13:30:41 -0700 Subject: [PATCH 7/9] removing searching --- ell-studio/src/hooks/useBackend.js | 47 +++++++++++++++--------------- src/ell/stores/sql.py | 28 ------------------ 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index edb5e3f6..a19fe214 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -70,36 +70,35 @@ export const useLatestLMPs = (page = 0, pageSize = 100) => { }; - const fetchTraces = async (lmps) => { - const baseUrl = API_BASE_URL - const response = await axios.get(`${baseUrl}/api/traces`); - // Filter out duplicate traces based on consumed and consumer - const uniqueTraces = response.data.reduce((acc, trace) => { - const key = `${trace.consumed}-${trace.consumer}`; - if (!acc[key]) { - acc[key] = trace; - } - return acc; - }, {}); - - // Convert the object of unique traces back to an array - const uniqueTracesArray = Object.values(uniqueTraces); - - // Filter out traces between LMPs that are not on the graph - const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); - const filteredTraces = uniqueTracesArray.filter(trace => - lmpIds.has(trace.consumed) && lmpIds.has(trace.consumer) - ); - - return filteredTraces; -} export const useTraces = (lmps) => { return useQuery({ queryKey: ['traces', lmps], - queryFn: () => fetchTraces(lmps), + queryFn: async () => { + const baseUrl = API_BASE_URL + const response = await axios.get(`${baseUrl}/api/traces`); + // Filter out duplicate traces based on consumed and consumer + const uniqueTraces = response.data.reduce((acc, trace) => { + const key = `${trace.consumed}-${trace.consumer}`; + if (!acc[key]) { + acc[key] = trace; + } + return acc; + }, {}); + + // Convert the object of unique traces back to an array + const uniqueTracesArray = Object.values(uniqueTraces); + + // Filter out traces between LMPs that are not on the graph + const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); + const filteredTraces = uniqueTracesArray.filter(trace => + lmpIds.has(trace.consumed) && lmpIds.has(trace.consumer) + ); + + return filteredTraces; + }, enabled: !!lmps && lmps.length > 0, }); }; diff --git a/src/ell/stores/sql.py b/src/ell/stores/sql.py index fce6aa78..f67f281b 100644 --- a/src/ell/stores/sql.py +++ b/src/ell/stores/sql.py @@ -252,34 +252,6 @@ def get_all_traces_leading_to(self, invocation_id: str) -> List[Dict[str, Any]]: return list(unique_traces.values()) - def search_invocations(self, q: str, skip: int = 0, limit: int = 10) -> List[Dict[str, Any]]: - with Session(self.engine) as session: - query = select(Invocation, SerializedLStr, SerializedLMP).join(SerializedLMP).outerjoin(SerializedLStr) - query = query.where( - or_( - Invocation.args.contains(q), - Invocation.kwargs.contains(q), - SerializedLStr.content.contains(q), - SerializedLMP.name.contains(q) - ) - ) - query = query.order_by(Invocation.created_at.desc()).offset(skip).limit(limit) - - results = session.exec(query).all() - - invocations = {} - for inv, lstr, lmp in results: - if inv.id not in invocations: - inv_dict = inv.model_dump() - inv_dict['lmp'] = lmp.model_dump() - invocations[inv.id] = inv_dict - invocations[inv.id]['results'] = [] - if lstr: - invocations[inv.id]['results'].append(dict(**lstr.model_dump(), __lstr=True)) - - return list(invocations.values()) - - class SQLiteStore(SQLStore): def __init__(self, storage_dir: str): os.makedirs(storage_dir, exist_ok=True) From 6af6a5246de9840a3cb112d94c244bee907db05b Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 13:31:03 -0700 Subject: [PATCH 8/9] rtemoving searching on frotend --- ell-studio/src/hooks/useBackend.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index a19fe214..c7d3d11b 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -103,15 +103,3 @@ export const useTraces = (lmps) => { }); }; -// New function for searching invocations -export const useSearchInvocations = (query, page = 0, pageSize = 100) => { - return useQuery({ - queryKey: ['searchInvocations', query, page, pageSize], - queryFn: async () => { - const skip = page * pageSize; - const response = await axios.post(`${API_BASE_URL}/api/invocations/search?q=${encodeURIComponent(query)}&skip=${skip}&limit=${pageSize}`); - return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - }, - enabled: !!query, - }); -}; \ No newline at end of file From 9d29c0d3b679a78c3bfe449570503c77c22e1d1c Mon Sep 17 00:00:00 2001 From: William Guss Date: Mon, 5 Aug 2024 13:59:02 -0700 Subject: [PATCH 9/9] refactored the api routes --- .../invocations/InvocationsTable.js | 1 - ell-studio/src/hooks/useBackend.js | 33 ++++---- ell-studio/src/pages/LMP.js | 11 +-- ell-studio/src/pages/Traces.js | 4 +- src/ell/studio/data_server.py | 81 +++++++++++-------- 5 files changed, 72 insertions(+), 58 deletions(-) diff --git a/ell-studio/src/components/invocations/InvocationsTable.js b/ell-studio/src/components/invocations/InvocationsTable.js index a7cee00d..239de446 100644 --- a/ell-studio/src/components/invocations/InvocationsTable.js +++ b/ell-studio/src/components/invocations/InvocationsTable.js @@ -6,7 +6,6 @@ import { getTimeAgo } from '../../utils/lmpUtils'; import VersionBadge from '../VersionBadge'; import { useNavigate } from 'react-router-dom'; import { lstrCleanStringify } from '../../utils/lstrCleanStringify'; -import { useInvocations } from '../../hooks/useBackend'; const InvocationsTable = ({ invocations, currentPage, setCurrentPage, pageSize, onSelectTrace, currentlySelectedTrace, omitColumns = [] }) => { const navigate = useNavigate(); diff --git a/ell-studio/src/hooks/useBackend.js b/ell-studio/src/hooks/useBackend.js index c7d3d11b..0553117a 100644 --- a/ell-studio/src/hooks/useBackend.js +++ b/ell-studio/src/hooks/useBackend.js @@ -8,7 +8,11 @@ export const useLMPs = (name, id) => { return useQuery({ queryKey: ['lmpDetails', name, id], queryFn: async () => { - const lmpResponse = await axios.get(`${API_BASE_URL}/api/lmps/${name}${id ? `/${id}` : ""}`); + const params = new URLSearchParams(); + if (name) params.append('name', name); + if (id) params.append('lmp_id', id); + + const lmpResponse = await axios.get(`${API_BASE_URL}/api/lmps?${params.toString()}`); const all_lmps_matching = lmpResponse.data; return all_lmps_matching .map((lmp) => ({ ...lmp, created_at: new Date(lmp.created_at) })) @@ -17,12 +21,19 @@ export const useLMPs = (name, id) => { }); }; -export const useInvocations = (name, id, page = 0, pageSize = 100) => { +export const useInvocations = (name, id, page = 0, pageSize = 50) => { return useQuery({ queryKey: ['invocations', name, id, page, pageSize], queryFn: async () => { const skip = page * pageSize; - const response = await axios.get(`${API_BASE_URL}/api/invocations/${name ? name : ""}${name && id ? `/${id}` : ""}?skip=${skip}&limit=${pageSize}`); + const params = new URLSearchParams(); + if (name) params.append('name', name); + if (id) params.append('lmp_id', id); + params.append('skip', skip); + params.append('limit', pageSize); + const response = await axios.get(`${API_BASE_URL}/api/invocations?${params.toString()}`); + + return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } }); @@ -33,8 +44,8 @@ export const useMultipleLMPs = (usesIds) => { queries: (usesIds || []).map(use => ({ queryKey: ['lmp', use], queryFn: async () => { - const useResponse = await axios.get(`${API_BASE_URL}/api/lmps/${use}`); - return useResponse.data[0]; + const useResponse = await axios.get(`${API_BASE_URL}/api/lmp/${use}`); + return useResponse.data; }, enabled: !!use, })), @@ -44,16 +55,7 @@ export const useMultipleLMPs = (usesIds) => { return { isLoading, data }; }; -export const useAllInvocations = (page = 0, pageSize = 100) => { - return useQuery({ - queryKey: ['allInvocations', page, pageSize], - queryFn: async () => { - const skip = page * pageSize; - const response = await axios.get(`${API_BASE_URL}/api/invocations?skip=${skip}&limit=${pageSize}`); - return response.data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - } - }); -}; + export const useLatestLMPs = (page = 0, pageSize = 100) => { @@ -102,4 +104,3 @@ export const useTraces = (lmps) => { enabled: !!lmps && lmps.length > 0, }); }; - diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index 445a7434..01eb4821 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -33,7 +33,10 @@ function LMP() { const { name, id } = useParams(); let [searchParams, setSearchParams] = useSearchParams(); const requestedInvocationId = searchParams.get("i"); + + const [currentPage, setCurrentPage] = useState(0); + // TODO: Could be expensive if you have a funct on of versions. const { data: versionHistory, isLoading: isLoadingLMP } = useLMPs(name); const lmp = useMemo(() => { if (!versionHistory) return null; @@ -44,10 +47,10 @@ function LMP() { } }, [versionHistory, id]); - console.log(name,id) const { data: invocations } = useInvocations(name, id); const { data: uses } = useMultipleLMPs(lmp?.uses); - console.log(uses) + + const [activeTab, setActiveTab] = useState("runs"); const [selectedTrace, setSelectedTrace] = useState(null); @@ -69,9 +72,7 @@ function LMP() { useEffect(() => { setSelectedTrace(requestedInvocation); }, [requestedInvocation]); - - const [currentPage, setCurrentPage] = useState(0); - const pageSize = 10; + const handleCopyCode = () => { const fullCode = `${lmp?.dependencies.trim()}\n\n${lmp?.source.trim()}`; diff --git a/ell-studio/src/pages/Traces.js b/ell-studio/src/pages/Traces.js index cb9d2cc8..f0570ca2 100644 --- a/ell-studio/src/pages/Traces.js +++ b/ell-studio/src/pages/Traces.js @@ -3,7 +3,7 @@ import { FiCopy, FiZap, FiEdit2, FiFilter, FiClock, FiColumns, FiPause, FiPlay } import InvocationsTable from '../components/invocations/InvocationsTable'; import InvocationsLayout from '../components/invocations/InvocationsLayout'; import { useNavigate, useLocation } from 'react-router-dom'; -import { useAllInvocations } from '../hooks/useBackend'; +import { useInvocations } from '../hooks/useBackend'; const Traces = () => { const [selectedTrace, setSelectedTrace] = useState(null); @@ -11,10 +11,10 @@ const Traces = () => { const navigate = useNavigate(); const location = useLocation(); - const { data: invocations, refetch , isLoading } = useAllInvocations(); const [currentPage, setCurrentPage] = useState(0); const pageSize = 10; + const { data: invocations, refetch , isLoading } = useInvocations(null, null, currentPage, pageSize); useEffect(() => { let intervalId; diff --git a/src/ell/studio/data_server.py b/src/ell/studio/data_server.py index e04bb7f3..e326ea93 100644 --- a/src/ell/studio/data_server.py +++ b/src/ell/studio/data_server.py @@ -44,54 +44,67 @@ def get_latest_lmps( ) return lmps - @app.get("/api/lmps/{name_or_id:path}") + # TOOD: Create a get endpoint to efficient get on the index with /api/lmp/ + @app.get("/api/lmp/{lmp_id}") + def get_lmp_by_id(lmp_id: str): + lmp = serializer.get_lmps(lmp_id=lmp_id)[0] + return lmp + + + @app.get("/api/lmps") def get_lmp( - name_or_id: str, + lmp_id: Optional[str] = Query(None), + name: Optional[str] = Query(None), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=100) ): - # Remove any leading slash if present - name_or_id = name_or_id.lstrip("/") + + filters = {} + if name: + filters['name'] = name + if lmp_id: + filters['lmp_id'] = lmp_id - # First, try to get by name - lmps_by_name = serializer.get_lmps(name=name_or_id, skip=skip, limit=limit) - if lmps_by_name: - return list(lmps_by_name) + lmps = serializer.get_lmps(skip=skip, limit=limit, **filters) - # If not found by name, try to get by ID - lmp_by_id = serializer.get_lmps(lmp_id=name_or_id) - if lmp_by_id: - return list(lmp_by_id) + if not lmps: + raise HTTPException(status_code=404, detail="LMP not found") - # If still not found, check if the last part of the path is a valid lmp_id - name_parts = name_or_id.split("/") - if len(name_parts) > 1: - potential_lmp_id = name_parts[-1] - potential_name = "/".join(name_parts[:-1]) - lmps = serializer.get_lmps(name=potential_name, lmp_id=potential_lmp_id, skip=skip, limit=limit) - if lmps: - return list(lmps) + return lmps + - raise HTTPException(status_code=404, detail="LMP not found") + + @app.get("/api/invocation/{invocation_id}") + def get_invocation( + invocation_id: str, + ): + invocation = serializer.get_invocations(id=invocation_id)[0] + return invocation @app.get("/api/invocations") - @app.get("/api/invocations/{name:path}") def get_invocations( - name: Optional[str] = None, + id: Optional[str] = Query(None), skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=100) + limit: int = Query(100, ge=1, le=100), + lmp_name: Optional[str] = Query(None), + lmp_id: Optional[str] = Query(None), ): lmp_filters = {} - if name: - name = name.lstrip("/") - name_parts = name.split("/") - - lmp_filters["name"] = name_parts[0] - if len(name_parts) > 1: - potential_lmp_id = name_parts[-1] - lmp_filters["lmp_id"] = potential_lmp_id - - invocations = serializer.get_invocations(lmp_filters=lmp_filters, skip=skip, limit=limit) + if lmp_name: + lmp_filters["name"] = lmp_name + if lmp_id: + lmp_filters["lmp_id"] = lmp_id + + invocation_filters = {} + if id: + invocation_filters["id"] = id + + invocations = serializer.get_invocations( + lmp_filters=lmp_filters, + filters=invocation_filters, + skip=skip, + limit=limit + ) return invocations @app.post("/api/invocations/search")