From b1781058a587ea8f74a047308cbe67b6cc5117de Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 4 Apr 2024 00:01:40 -0700 Subject: [PATCH 01/24] Pagination bug --- components/project/eval/eval.tsx | 4 ++-- components/project/eval/prompts.tsx | 4 ++-- lib/services/trace_service.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/project/eval/eval.tsx b/components/project/eval/eval.tsx index 8441bfdc..7b139ddd 100644 --- a/components/project/eval/eval.tsx +++ b/components/project/eval/eval.tsx @@ -62,9 +62,9 @@ export default function Eval({ email }: { email: string }) { } if (result) { if (data) { - setData((prevData: any) => [...prevData, ...result.prompts.result]); + setData((prevData: any) => [...prevData, ...result?.prompts?.result || []]); } else { - setData(result.prompts.result); + setData(result?.prompts?.result || []); } } setPage((currentPage) => currentPage + 1); diff --git a/components/project/eval/prompts.tsx b/components/project/eval/prompts.tsx index bb385183..9ea37d51 100644 --- a/components/project/eval/prompts.tsx +++ b/components/project/eval/prompts.tsx @@ -52,9 +52,9 @@ export default function Prompts({ email }: { email: string }) { } if (result) { if (data) { - setData((prevData: any) => [...prevData, ...result.prompts.result]); + setData((prevData: any) => [...prevData, ...result?.prompts?.result || []]); } else { - setData(result.prompts.result); + setData(result?.prompts?.result || []); } } setPage((currentPage) => currentPage + 1); diff --git a/lib/services/trace_service.ts b/lib/services/trace_service.ts index 1761aa06..ad7c35c7 100644 --- a/lib/services/trace_service.ts +++ b/lib/services/trace_service.ts @@ -244,7 +244,7 @@ export class TraceService implements ITraceService { const md = { page, page_size: pageSize, total_pages: totalPages }; if (page! > totalPages) { - throw Error("Page number is greater than total pages"); + page = totalPages; } const query = sql.select( `* FROM ${project_id} WHERE attributes LIKE '%${attribute}%' ORDER BY 'start_time' DESC LIMIT ${pageSize} OFFSET ${ @@ -325,7 +325,7 @@ export class TraceService implements ITraceService { const md = { page, page_size: pageSize, total_pages: totalPages }; if (page! > totalPages) { - throw Error("Page number is greater than total pages"); + page = totalPages; } const query = sql.select( `* FROM ${project_id} ORDER BY 'createdAt' DESC LIMIT ${pageSize} OFFSET ${ From 0e963e226af17ffdba6282496d1979c6e30c20ad Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 4 Apr 2024 17:13:49 -0700 Subject: [PATCH 02/24] Bug fix --- app/api/user/route.ts | 1 - components/project/traces.tsx | 2 -- lib/utils.ts | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/api/user/route.ts b/app/api/user/route.ts index d7e95f34..a10d34bf 100644 --- a/app/api/user/route.ts +++ b/app/api/user/route.ts @@ -83,7 +83,6 @@ export async function PUT(req: NextRequest) { } if ("status" in data) { - console.log("updating status"); const user = await prisma.user.update({ where: { id, diff --git a/components/project/traces.tsx b/components/project/traces.tsx index 523aa352..1f56c03e 100644 --- a/components/project/traces.tsx +++ b/components/project/traces.tsx @@ -80,8 +80,6 @@ export default function Traces({ email }: { email: string }) { } // Merge the new data with the existing data - console.log("currentData", currentData); - console.log("newData", newData); if (currentData.length > 0) { const updatedData = [...currentData, ...newData]; diff --git a/lib/utils.ts b/lib/utils.ts index 584d22f2..2c6fd519 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -326,6 +326,7 @@ export function calculatePriceFromUsage( output_tokens: number; } ): any { + if (!model) return { total: 0, input: 0, output: 0 }; if (vendor === "openai") { const costTable = OPENAI_PRICING[model.includes("gpt-4") ? "gpt-4" : model]; if (costTable) { From 51e3310f5339d0b947a2b086ce365c0b96122cf7 Mon Sep 17 00:00:00 2001 From: Darshit Suratwala Date: Tue, 23 Apr 2024 17:10:04 +0530 Subject: [PATCH 03/24] chore: add docker cmd --- Dockerfile | 2 ++ docker-compose.yaml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 151f223d..e1555717 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,3 +8,5 @@ COPY . . RUN npm install EXPOSE 3000 + +CMD [ "/bin/sh", "-c", "npm run create-tables && npm run dev" ] diff --git a/docker-compose.yaml b/docker-compose.yaml index a7447299..078e6d4e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,7 +10,6 @@ services: working_dir: /app env_file: - .env - command: /bin/sh -c "npm run create-tables && npm run dev" ports: - "3000:3000" # Uncmment this for development From 0b424f2c80432dab4748ce584ce77fcf8e4260fc Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman <105607645+karthikscale3@users.noreply.github.com> Date: Wed, 24 Apr 2024 08:14:09 -0700 Subject: [PATCH 04/24] Compatibility fixes for SDK version 2.0.0 (#69) * Pagination bug * Bug fix * Fix for schema changes * Render tool calling --- .../evaluations/[test_id]/page.tsx | 109 +++++++++------- components/evaluations/evaluation-row.tsx | 14 +- components/project/traces/trace-row.tsx | 14 +- components/shared/hover-cell.tsx | 49 ++++++- components/shared/llm-view.tsx | 123 +++++++++--------- components/shared/user-logo.tsx | 7 + components/shared/vendor-metadata.tsx | 76 +++++++++-- lib/pii.ts | 6 +- lib/utils.ts | 10 ++ package-lock.json | 27 ++++ package.json | 1 + public/cohere.png | Bin 0 -> 4811 bytes 12 files changed, 292 insertions(+), 144 deletions(-) create mode 100644 components/shared/user-logo.tsx create mode 100644 public/cohere.png diff --git a/app/(protected)/project/[project_id]/evaluations/[test_id]/page.tsx b/app/(protected)/project/[project_id]/evaluations/[test_id]/page.tsx index 5eabf586..da6bb2e5 100644 --- a/app/(protected)/project/[project_id]/evaluations/[test_id]/page.tsx +++ b/app/(protected)/project/[project_id]/evaluations/[test_id]/page.tsx @@ -2,6 +2,7 @@ import { ScaleType } from "@/components/evaluations/eval-scale-picker"; import { RangeScale } from "@/components/evaluations/range-scale"; +import UserLogo from "@/components/shared/user-logo"; import { VendorLogo } from "@/components/shared/vendor-metadata"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; @@ -10,6 +11,7 @@ import { cn, extractSystemPromptFromLlmInputs, formatDateTime, + safeStringify, } from "@/lib/utils"; import { Cross1Icon, EnterIcon } from "@radix-ui/react-icons"; import { ProgressCircle } from "@tremor/react"; @@ -23,7 +25,6 @@ import { } from "lucide-react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; -import Markdown from "react-markdown"; import { useQuery, useQueryClient } from "react-query"; import { toast } from "sonner"; @@ -315,7 +316,7 @@ export default function Page() { size="md" value={(page / totalPages) * 100} > - + {Math.round((page / totalPages) * 100)}% @@ -348,8 +349,8 @@ export default function Page() { className={cn( "ml-2 text-xs px-1 py-[2px] rounded-md", evaluationsData?.evaluations[0]?.id - ? "bg-green-400" - : "bg-orange-400" + ? "bg-green-400 dark:bg-green-800" + : "bg-orange-400 dark:bg-orange-800" )} > {evaluationsData?.evaluations[0]?.id @@ -474,49 +475,69 @@ function ConversationView({ span }: { span: any }) { if (!prompts && !responses) return

No data found

; return ( -
+
{prompts?.length > 0 && - JSON.parse(prompts).map((prompt: any, i: number) => ( -
-

- {prompt?.role - ? prompt?.role === "function" - ? `${prompt?.role} - ${prompt?.name}` - : prompt?.role - : "Input"} - : - {prompt?.content - ? " (content)" - : prompt?.function_call - ? " (function call)" - : ""} -

{" "} - - {prompt?.content - ? prompt?.content - : prompt?.function_call - ? JSON.stringify(prompt?.function_call) - : "No input found"} - -
- ))} + JSON.parse(prompts).map((prompt: any, i: number) => { + const role = prompt?.role ? prompt?.role?.toLowerCase() : "User"; + const content = prompt?.content + ? safeStringify(prompt?.content) + : prompt?.function_call + ? safeStringify(prompt?.function_call) + : "No input found"; + return ( +
+
+ {role === "user" ? ( + + ) : ( + + )} +

{role}

+ {role === "system" && ( +

+ Prompt +

+ )} +
+
+
+ ); + })} {responses?.length > 0 && - JSON.parse(responses).map((response: any, i: number) => ( -
-
- -

- {response?.message?.role || "Output"}: -

{" "} + JSON.parse(responses).map((response: any, i: number) => { + const role = + response?.role?.toLowerCase() || + response?.message?.role || + "Assistant"; + const content = + safeStringify(response?.content) || + safeStringify(response?.message?.content) || + safeStringify(response?.text) || + "No output found"; + return ( +
+
+ {role === "user" ? ( + + ) : ( + + )} +

{role}

+
+
- - {response?.message?.content || - response?.text || - response?.content || - "No output found"} - -
- ))} + ); + })}
); } diff --git a/components/evaluations/evaluation-row.tsx b/components/evaluations/evaluation-row.tsx index 31772c0a..f303f5cc 100644 --- a/components/evaluations/evaluation-row.tsx +++ b/components/evaluations/evaluation-row.tsx @@ -215,18 +215,12 @@ export default function EvaluationRow({

{model}

0 ? JSON.parse(prompts)[0]?.content : ""} + className="flex items-center text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" + values={prompts?.length > 0 ? JSON.parse(prompts) : []} /> 0 - ? JSON.parse(responses)[0]?.message?.content || - JSON.parse(responses)[0]?.text || - JSON.parse(responses)[0]?.content - : "" - } + className="flex items-center text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" + values={responses?.length > 0 ? JSON.parse(responses) : []} />

{cost.total.toFixed(6) !== "0.000000" diff --git a/components/project/traces/trace-row.tsx b/components/project/traces/trace-row.tsx index a340926d..64922495 100644 --- a/components/project/traces/trace-row.tsx +++ b/components/project/traces/trace-row.tsx @@ -126,18 +126,12 @@ export const TraceRow = ({

{model}

0 ? JSON.parse(prompts)[0]?.content : ""} - className="max-w-fit text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" + values={prompts?.length > 0 ? JSON.parse(prompts) : []} + className="flex items-center max-w-fit text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" /> 0 - ? JSON.parse(responses)[0]?.message?.content || - JSON.parse(responses)[0]?.text || - JSON.parse(responses)[0]?.content - : "" - } - className="max-w-fit text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" + values={responses?.length > 0 ? JSON.parse(responses) : []} + className="flex items-center max-w-fit text-xs h-10 truncate overflow-y-scroll font-semibold col-span-2" />

{userId}

diff --git a/components/shared/hover-cell.tsx b/components/shared/hover-cell.tsx index 38e87d6c..4f9132fa 100644 --- a/components/shared/hover-cell.tsx +++ b/components/shared/hover-cell.tsx @@ -3,22 +3,61 @@ import { HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; -import Markdown from "react-markdown"; +import { safeStringify } from "@/lib/utils"; export function HoverCell({ - value, + values, className, }: { - value: string; + values: any[]; className?: string; }) { + const contents = values.map((value, i) => { + const role = value?.role + ? value?.role?.toLowerCase() + : value?.message?.role + ? value?.message?.role + : "User"; + const content = value?.content + ? safeStringify(value?.content) + : value?.function_call + ? safeStringify(value?.function_call) + : value?.message?.content + ? safeStringify(value?.message?.content) + : value?.text + ? safeStringify(value?.text) + : ""; + return { role, content }; + }); + + if (contents.length === 0) { + return null; + } + return ( - +

- {value} +
+ {contents.map((item, i) => ( +
+

+ {item.role} +

+
+
+ ))} +
); diff --git a/components/shared/llm-view.tsx b/components/shared/llm-view.tsx index 5b255b8a..be76166c 100644 --- a/components/shared/llm-view.tsx +++ b/components/shared/llm-view.tsx @@ -1,8 +1,7 @@ "use client"; import detectPII from "@/lib/pii"; -import { cn } from "@/lib/utils"; -import Markdown from "react-markdown"; +import { cn, safeStringify } from "@/lib/utils"; export const LLMView = ({ prompts, @@ -18,66 +17,68 @@ export const LLMView = ({ return (
{prompts?.length > 0 && - JSON.parse(prompts).map((prompt: any, i: number) => ( -

- - {prompt?.role - ? prompt?.role === "function" - ? `${prompt?.role} - ${prompt?.name}` - : prompt?.role - : "Q"} - : - {prompt?.content - ? " (content)" - : prompt?.function_call - ? " (function call)" - : ""} - {" "} - 0 && - "underline decoration-red-600 decoration-[3px]" - )} + JSON.parse(prompts).map((prompt: any, i: number) => { + const role = prompt?.role ? prompt?.role?.toLowerCase() : "User"; + const content = prompt?.content + ? prompt?.content + : prompt?.function_call + ? prompt?.function_call + : ""; + return ( +

- {prompt?.content - ? prompt?.content - : prompt?.function_call - ? JSON.stringify(prompt?.function_call) - : ""} - -

- ))} -
- - {JSON.parse(responses)[0]?.message?.role || "Assistant"}: - {" "} - {responses?.length > 0 ? ( - 0 && - "underline decoration-red-600 decoration-[3px]" - )} - > - {JSON.parse(responses)[0]?.message?.content || - JSON.parse(responses)[0]?.text || - JSON.parse(responses)[0]?.content || - JSON.parse(responses).message?.content} - - ) : ( - "" - )} - {Evaluate && } -
+ + {role} + {" "} +
0 && + "underline decoration-red-600 decoration-[3px]" + )} + dangerouslySetInnerHTML={{ __html: safeStringify(content) }} + /> +
+ ); + })} + {responses?.length > 0 && + JSON.parse(responses).map((response: any, i: number) => { + const role = + response?.role?.toLowerCase() || + response?.message?.role || + "Assistant"; + const content = + response?.content || + response?.message?.content || + response?.text || + ""; + + return ( +
+ + {role} + +
0 && + "underline decoration-red-600 decoration-[3px]" + )} + dangerouslySetInnerHTML={{ __html: safeStringify(content) }} + /> + {Evaluate && } +
+ ); + })}
); }; diff --git a/components/shared/user-logo.tsx b/components/shared/user-logo.tsx new file mode 100644 index 00000000..422b1d70 --- /dev/null +++ b/components/shared/user-logo.tsx @@ -0,0 +1,7 @@ +export default function UserLogo() { + return ( +
+ U +
+ ); +} diff --git a/components/shared/vendor-metadata.tsx b/components/shared/vendor-metadata.tsx index 54a145ee..4508ec0b 100644 --- a/components/shared/vendor-metadata.tsx +++ b/components/shared/vendor-metadata.tsx @@ -1,3 +1,4 @@ +import { cn } from "@/lib/utils"; import { StackIcon } from "@radix-ui/react-icons"; import Image from "next/image"; @@ -37,6 +38,10 @@ export function vendorBadgeColor(vendor: string) { return "bg-indigo-500"; } + if (vendor.includes("cohere")) { + return "bg-red-500"; + } + return "bg-gray-500"; } @@ -73,6 +78,10 @@ export function vendorColor(vendor: string) { return "bg-indigo-200"; } + if (vendor.includes("cohere")) { + return "bg-red-200"; + } + return "bg-gray-800"; } @@ -87,7 +96,13 @@ export function serviceTypeColor(serviceType: string) { return "bg-gray-500"; } -export function VendorLogo({ span }: { span: Span }) { +export function VendorLogo({ + span, + variant = "default", +}: { + span: Span; + variant?: string; +}) { const attributes = span.attributes ? JSON.parse(span.attributes) : {}; let serviceName = ""; if (attributes["langtrace.service.name"]) { @@ -102,7 +117,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/perplexity.png" width={20} height={20} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -115,7 +133,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/openai.svg" width={20} height={20} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -128,7 +149,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/anthropic.png" width={30} height={30} - className="p-[3px] rounded-md" + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -141,7 +165,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/pinecone.png" width={20} height={20} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -154,7 +181,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/chroma.png" width={25} height={25} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -167,7 +197,10 @@ export function VendorLogo({ span }: { span: Span }) { src="/langchain.svg" width={30} height={30} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} /> ); } @@ -180,15 +213,38 @@ export function VendorLogo({ span }: { span: Span }) { src="/llamaindex.svg" width={60} height={80} - className={`${color} p-[3px] rounded-sm`} + className={cn( + `${color} p-[3px]`, + variant === "circular" ? "rounded-full" : "rounded-md" + )} + /> + ); + } + + if (span.name.includes("cohere") || serviceName.includes("cohere")) { + const color = vendorColor("cohere"); + return ( + Cohere Logo ); } - const color = vendorColor("langtrace"); return (
- +
); } diff --git a/lib/pii.ts b/lib/pii.ts index 1d631c9a..4577e1eb 100644 --- a/lib/pii.ts +++ b/lib/pii.ts @@ -1,8 +1,5 @@ export default function detectPII(text: string): string[] { try { - JSON.parse(text); - return []; - } catch (e) { // Define regular expressions for various types of PII const patterns = { phoneNumber: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, // Simplified phone number pattern (US-centric) @@ -24,7 +21,8 @@ export default function detectPII(text: string): string[] { foundPII.push(...matches.map((match) => `${type}: ${match}`)); } } - return foundPII; + } catch (error) { + return []; } } diff --git a/lib/utils.ts b/lib/utils.ts index 3935dfe9..9c04bda9 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -3,6 +3,7 @@ import { clsx, type ClassValue } from "clsx"; import { createHash, randomBytes } from "crypto"; import { TiktokenEncoding, getEncoding } from "js-tiktoken"; import { NextResponse } from "next/server"; +import { prettyPrintJson } from "pretty-print-json"; import { twMerge } from "tailwind-merge"; import { Span } from "./clients/scale3_clickhouse/models/span"; import { @@ -405,3 +406,12 @@ export const getChartColor = (value: number) => { return "green"; } }; + +export function safeStringify(value: any): string { + // Check if the value is already a string + if (typeof value === "string") { + return value; + } + // If it's not a string, stringify it + return prettyPrintJson.toHtml(value); +} diff --git a/package-lock.json b/package-lock.json index d5bd2206..4568a4a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "f": "^1.4.0", "framer-motion": "^11.0.5", "fs": "^0.0.1-security", + "interweave": "^13.1.0", "js-tiktoken": "^1.0.10", "langchain": "^0.1.20", "lucide-react": "^0.323.0", @@ -63,6 +64,7 @@ "next-themes": "^0.2.1", "openai": "^4.26.0", "pdf-parse": "^1.1.1", + "pretty-print-json": "^3.0.0", "prism": "^4.1.2", "prismjs": "^1.29.0", "react": "^18", @@ -5727,6 +5729,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -7123,6 +7130,21 @@ "node": ">=12" } }, + "node_modules/interweave": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/interweave/-/interweave-13.1.0.tgz", + "integrity": "sha512-JIDq0+2NYg0cgL7AB26fBcV0yZdiJvPDBp+aF6k8gq6Cr1kH5Gd2/Xqn7j8z+TGb8jCWZn739jzalCz+nPYwcA==", + "dependencies": { + "escape-html": "^1.0.3" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/milesjohnson" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10448,6 +10470,11 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, + "node_modules/pretty-print-json": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pretty-print-json/-/pretty-print-json-3.0.0.tgz", + "integrity": "sha512-1993HBv7RNqoPNgaajOK3RLm544t3jongECDFw6Y5faYiBeg2tg62jDT1EDmXRRDn296ykH49A8tphkQBHUSTA==" + }, "node_modules/prism": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/prism/-/prism-4.1.2.tgz", diff --git a/package.json b/package.json index 923018f0..8d8ba00f 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "next-themes": "^0.2.1", "openai": "^4.26.0", "pdf-parse": "^1.1.1", + "pretty-print-json": "^3.0.0", "prism": "^4.1.2", "prismjs": "^1.29.0", "react": "^18", diff --git a/public/cohere.png b/public/cohere.png new file mode 100644 index 0000000000000000000000000000000000000000..db1cc73fbc12e8f4c219ea750be6cb956532577c GIT binary patch literal 4811 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 Date: Sun, 28 Apr 2024 12:18:32 -0700 Subject: [PATCH 05/24] Support for Langgraph, Qdrant & Groq (#73) * Pagination bug * Bug fix * Add langgraph support * QDrant support * Add Groq support * update README * update README --- README.md | 3 + components/project/traces/trace-row.tsx | 34 +- components/shared/langgraph-view.tsx | 122 +++++++ components/shared/vendor-metadata.tsx | 48 +++ components/traces/trace_graph.tsx | 6 + package-lock.json | 401 ++++++++++++++++++++++-- package.json | 1 + public/groq.png | Bin 0 -> 6254 bytes public/qdrant.png | Bin 0 -> 8110 bytes 9 files changed, 593 insertions(+), 22 deletions(-) create mode 100644 components/shared/langgraph-view.tsx create mode 100644 public/groq.png create mode 100644 public/qdrant.png diff --git a/README.md b/README.md index e0019af4..64709195 100644 --- a/README.md +++ b/README.md @@ -215,10 +215,13 @@ Langtrace automatically captures traces from the following vendors: | OpenAI | LLM | :white_check_mark: | :white_check_mark: | | Anthropic | LLM | :white_check_mark: | :white_check_mark: | | Azure OpenAI | LLM | :white_check_mark: | :white_check_mark: | +| Cohere | LLM | :white_check_mark: | :white_check_mark: | +| Groq | LLM | :x: | :white_check_mark: | | Langchain | Framework | :x: | :white_check_mark: | | LlamaIndex | Framework | :white_check_mark: | :white_check_mark: | | Pinecone | Vector Database | :white_check_mark: | :white_check_mark: | | ChromaDB | Vector Database | :white_check_mark: | :white_check_mark: | +| QDrant | Vector Database | :x: | :white_check_mark: | --- diff --git a/components/project/traces/trace-row.tsx b/components/project/traces/trace-row.tsx index 64922495..4116d950 100644 --- a/components/project/traces/trace-row.tsx +++ b/components/project/traces/trace-row.tsx @@ -1,5 +1,6 @@ "use client"; +import LanggraphView from "@/components/shared/langgraph-view"; import { calculateTotalTime, convertTracesToHierarchy, @@ -36,9 +37,16 @@ export const TraceRow = ({ let prompts: any = {}; let responses: any = {}; let cost = { total: 0, input: 0, output: 0 }; + let langgraph = false; for (const span of trace) { if (span.attributes) { const attributes = JSON.parse(span.attributes); + if (attributes["langtrace.service.name"]) { + vendor = attributes["langtrace.service.name"].toLowerCase(); + if (vendor === "langgraph") { + langgraph = true; + } + } userId = attributes["user.id"]; if (attributes["llm.prompts"] && attributes["llm.responses"]) { prompts = attributes["llm.prompts"]; @@ -46,7 +54,6 @@ export const TraceRow = ({ } if (attributes["llm.token.counts"]) { model = attributes["llm.model"]; - vendor = attributes["langtrace.service.name"].toLowerCase(); const currentcounts = JSON.parse(attributes["llm.token.counts"]); tokenCounts = { input_tokens: tokenCounts.input_tokens @@ -213,6 +220,26 @@ export const TraceRow = ({ )} + {langgraph && ( + + )}
{selectedTab === "trace" && ( @@ -235,6 +262,11 @@ export const TraceRow = ({
)} + {selectedTab === "langgraph" && ( +
+ +
+ )}
)}
diff --git a/components/shared/langgraph-view.tsx b/components/shared/langgraph-view.tsx new file mode 100644 index 00000000..39344e4a --- /dev/null +++ b/components/shared/langgraph-view.tsx @@ -0,0 +1,122 @@ +import { Span } from "@/lib/clients/scale3_clickhouse/models/span"; +import ReactFlow, { + Background, + BaseEdge, + Controls, + EdgeLabelRenderer, + getBezierPath, +} from "reactflow"; +import "reactflow/dist/style.css"; + +const CustomEdge = ({ id, data, ...props }: any) => { + const [edgePath, labelX, labelY] = getBezierPath(props); + + return ( + <> + + +
+ {data.label} +
+
+ + ); +}; + +export default function LanggraphView({ trace }: { trace: Span[] }) { + // construct the nodes and edges from the trace + let x = 0; + let y = 0; + const nodes = [ + { id: "__start__", data: { label: "Start" }, position: { x: 0, y: 0 } }, + ]; + const edges = []; + try { + for (const span of trace) { + const attributes = JSON.parse(span.attributes); + if (Object.keys(attributes).length > 0) { + const vendor = attributes["langtrace.service.name"].toLowerCase(); + const node = attributes["langgraph.node"]; + const edge = attributes["langgraph.edge"]; + const task = attributes["langgraph.task.name"]; + if (vendor === "langgraph" && node) { + x += 200; + y += 200; + const pNode = JSON.parse(node); + nodes.push({ + id: pNode?.name || span.span_id, + data: { + label: + `${pNode?.name} (action: ${pNode.action})}` || span.span_id, + }, + position: { x, y }, + }); + } + + if (vendor === "langgraph" && edge) { + const pEdge = JSON.parse(edge); + if (task === "add_conditional_edges") { + const pathMap = pEdge?.path_map; + if (pathMap) { + for (const k of Object.keys(pathMap)) { + edges.push({ + id: `${pEdge?.source || "source"}-${ + pathMap[k] || "destination" + }`, + data: { label: `${pEdge?.path} (output: ${k})` || "" }, + source: pEdge?.source || "source", + target: pathMap[k] || "destination", + type: "custom", + }); + } + } + } else { + edges.push({ + id: `${pEdge?.source || "source"}-${ + pEdge?.destination || "destination" + }`, + source: pEdge?.source || "source", + target: pEdge?.destination || "destination", + }); + } + } + } + } + } catch (e) { + console.error(e); + } + + nodes.push({ + id: "__end__", + data: { label: "End" }, + position: { x: 0, y: y + 200 }, + }); + + return ( +
+ + + + +
+ ); +} diff --git a/components/shared/vendor-metadata.tsx b/components/shared/vendor-metadata.tsx index 4508ec0b..233331da 100644 --- a/components/shared/vendor-metadata.tsx +++ b/components/shared/vendor-metadata.tsx @@ -42,6 +42,14 @@ export function vendorBadgeColor(vendor: string) { return "bg-red-500"; } + if (vendor.includes("qdrant")) { + return "bg-grey-500"; + } + + if (vendor.includes("qdrant")) { + return "bg-grey-500"; + } + return "bg-gray-500"; } @@ -82,6 +90,14 @@ export function vendorColor(vendor: string) { return "bg-red-200"; } + if (vendor.includes("qdrant")) { + return "bg-grey-200"; + } + + if (vendor.includes("groq")) { + return "bg-slate-200"; + } + return "bg-gray-800"; } @@ -109,6 +125,22 @@ export function VendorLogo({ serviceName = attributes["langtrace.service.name"].toLowerCase(); } + if (span.name.includes("groq") || serviceName.includes("groq")) { + const color = vendorColor("groq"); + return ( + Groq Logo + ); + } + if (span.name.includes("perplexity") || serviceName.includes("perplexity")) { const color = vendorColor("perplexity"); return ( @@ -237,6 +269,22 @@ export function VendorLogo({ ); } + if (span.name.includes("qdrant") || serviceName.includes("qdrant")) { + const color = vendorColor("qdrant"); + return ( + Qdrant Logo + ); + } + return (
= ({ color = "bg-indigo-500"; else if (span.name.includes("langchain") || serviceName.includes("langchain")) color = "bg-purple-500"; + else if (span.name.includes("cohere") || serviceName.includes("cohere")) + color = "bg-red-500"; + else if (span.name.includes("qdrant") || serviceName.includes("qdrant")) + color = "bg-grey-500"; + else if (span.name.includes("groq") || serviceName.includes("groq")) + color = "bg-slate-500"; else if ( span.name.includes("llamaindex") || serviceName.includes("llamaindex") diff --git a/package-lock.json b/package-lock.json index 4568a4a0..c936e48f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,6 @@ "f": "^1.4.0", "framer-motion": "^11.0.5", "fs": "^0.0.1-security", - "interweave": "^13.1.0", "js-tiktoken": "^1.0.10", "langchain": "^0.1.20", "lucide-react": "^0.323.0", @@ -76,6 +75,7 @@ "react-markdown": "^9.0.1", "react-query": "^3.39.3", "react-syntax-highlighter": "^15.5.0", + "reactflow": "^11.11.2", "shiki-processor": "^0.1.3", "sonner": "^1.3.1", "sql-bricks": "^3.0.1", @@ -3109,6 +3109,102 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@reactflow/background": { + "version": "11.3.12", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.12.tgz", + "integrity": "sha512-jBuWVb43JQy5h4WOS7G0PU8voGTEJNA+qDmx8/jyBtrjbasTesLNfQvboTGjnQYYiJco6mw5vrtQItAJDNoIqw==", + "dependencies": { + "@reactflow/core": "11.11.2", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.12", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.12.tgz", + "integrity": "sha512-L9F3+avFRShoprdT+5oOijm5gVsz2rqWCXBzOAgD923L1XFGIspdiHLLf8IlPGsT+mfl0GxbptZhaEeEzl1e3g==", + "dependencies": { + "@reactflow/core": "11.11.2", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.2", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.2.tgz", + "integrity": "sha512-+GfgyskweL1PsgRSguUwfrT2eDotlFgaKfDLm7x0brdzzPJY2qbCzVetaxedaiJmIli3817iYbILvE9qLKwbRA==", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.12", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.12.tgz", + "integrity": "sha512-SRDU77c2PCF54PV/MQfkz7VOW46q7V1LZNOQlXAp7dkNyAOI6R+tb9qBUtUJOvILB+TCN6pRfD9fQ+2T99bW3Q==", + "dependencies": { + "@reactflow/core": "11.11.2", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz", + "integrity": "sha512-6LHJGuI1zHyRrZHw5gGlVLIWnvVxid9WIqw8FMFSg+oF2DuS3pAPwSoZwypy7W22/gDNl9eD1Dcl/OtFtDFQ+w==", + "dependencies": { + "@reactflow/core": "11.11.2", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz", + "integrity": "sha512-4kJRvNna/E3y2MZW9/80wTKwkhw4pLJiz3D5eQrD13XcmojSb1rArO9CiwyrI+rMvs5gn6NlCFB4iN1F+Q+lxQ==", + "dependencies": { + "@reactflow/core": "11.11.2", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/@remixicon/react": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.2.0.tgz", @@ -3267,21 +3363,142 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -3295,6 +3512,21 @@ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, "node_modules/@types/d3-scale": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", @@ -3303,6 +3535,16 @@ "@types/d3-time": "*" } }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" + }, "node_modules/@types/d3-shape": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", @@ -3316,11 +3558,33 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, + "node_modules/@types/d3-transition": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3351,6 +3615,11 @@ "@types/node": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -4635,6 +4904,11 @@ "node": ">=6" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, "node_modules/clickhouse": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/clickhouse/-/clickhouse-2.6.0.tgz", @@ -5125,6 +5399,26 @@ "node": ">=12" } }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-dsv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", @@ -5196,6 +5490,14 @@ "node": ">=12" } }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -5237,6 +5539,39 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5729,11 +6064,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -7130,21 +7460,6 @@ "node": ">=12" } }, - "node_modules/interweave": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/interweave/-/interweave-13.1.0.tgz", - "integrity": "sha512-JIDq0+2NYg0cgL7AB26fBcV0yZdiJvPDBp+aF6k8gq6Cr1kH5Gd2/Xqn7j8z+TGb8jCWZn739jzalCz+nPYwcA==", - "dependencies": { - "escape-html": "^1.0.3" - }, - "funding": { - "type": "ko-fi", - "url": "https://ko-fi.com/milesjohnson" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10909,6 +11224,23 @@ "react-dom": ">=16.8.0" } }, + "node_modules/reactflow": { + "version": "11.11.2", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.2.tgz", + "integrity": "sha512-o1fT3stSdhzW+SedCGNSmEvZvULZygZIMLyW67NcWNZrgwx1wuJfzLg5fuQ0Nzf389wItumZX/zP3zdaPX7lEw==", + "dependencies": { + "@reactflow/background": "11.3.12", + "@reactflow/controls": "11.2.12", + "@reactflow/core": "11.11.2", + "@reactflow/minimap": "11.7.12", + "@reactflow/node-resizer": "2.2.12", + "@reactflow/node-toolbar": "1.3.12" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -13134,6 +13466,33 @@ "zod": "^3.22.4" } }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 8d8ba00f..11557f41 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react-markdown": "^9.0.1", "react-query": "^3.39.3", "react-syntax-highlighter": "^15.5.0", + "reactflow": "^11.11.2", "shiki-processor": "^0.1.3", "sonner": "^1.3.1", "sql-bricks": "^3.0.1", diff --git a/public/groq.png b/public/groq.png new file mode 100644 index 0000000000000000000000000000000000000000..694b427c432e89d2ec0696cd72498c00b8a550b3 GIT binary patch literal 6254 zcmb_fcU)81v%ewqqV!%u6=|VE5JIF#i4+0pHT0HHLK75FmfjTUeJM%_MY>AwC?Ft3 zq$8jp(v~jp;_klP-+S-hcRrsvGiPSLGxyB7ckaD0`nnoa?7pj38MRn^y0RfXvLV4Pe%905QxCe?(*)Nqh7&*E7!!UaTF9I-$Mx(O7Bd@Yj>xit174%1ZkIq%EIaa43aXlxD9I^iU2023g=MHdh36W?EK;K~5tLGc=vQafJZ&W9Wwfutm7I_c>H{gbie(2*< zx)0F}pJ6?VhNNvu8H9e&y2^IitPzMxfyb};KFu@;gP1%Y&fE)4TMf0)ZFCTfRX9p3UOLn_ z+Ww@=HnatvmM?HE_TEYQmoL|bnLg)m+iD2zhQEz(rVe^o!ziYarkq1DHNqsh8ihS+ zS?!Km?^-1x9NI`c-2H8@mOS_R?}}HG*CNr6Dp4YN$s|n0 zmRx#tYwEp&2R#&0u)=%$A-fC^_8J6G04DP&vlSX3eB9U;2@mAoAtPXi5Pu81{+;3d zhiEmkBx+9k+uSE~Jn#Vy&JF0++LMrhP!6Xxjo@T~t>N^90Rlbl5s)2(%^K-Y6wy6K z+d(%#dP$QDW@B2+YdbOH$D=GIoU+6#NRE)a#9wXX!4Yg4_F{A#Sc?$SCqg6@c-kXTr+ueM?V_~H_ zhH^M{xXsb2yh1f^=F0EO;m#4v(LQj#e?l}KAZ@F|FicG`F17#l?uq)+#3z-iZo$`e zMRgRw_)grpQ<`@Dj<-1a& z57yYll&F_1RH)#t4?0LQK<@Hf&P&8^!?;KZ3n8>U;e6sOPWhne7UsJobsZu4l-ix- zHH=1KI<~?yWJh7(0uVd+MJLRQrTz)q5~roo{1VYulAR7`!rPjp429eeq9st_Gu$Jj z;Jw=fPrdkvwNwm}v1-wp$z}rt#q?&s89^+PSsj#p2w@49Y6{7>26&5^f)YYhB#ekA zRjBSEvC>0`Oc!R>C|#9_F;^Gv*VI;#X+@J?(e<243D$Qo1-V}-umCtivh+!&?GZEB z-tMWXWLjiQEzFe6IP$)|pDEqj(dio9%IoLRbl$AG{oBjr*E(0d#o$!=PoTZRySaf% z3y*EOxt1?Zn!Uw`#q-4}#TCWqaT0z@)V!My1u9ZR2lBpXN+u~jd7JY#VNR`0<%d9Msf&K4;KFlxZ2?Dk zNsfUA4B4U=R8(2%QU1M<9Veon_rkL3t={S5ecrVsp)8?BA(7M=A=*?&p<70}Wpnp} zR7(s*%N~7naO3o2X^4Cfd6s;3JGFGPV90o_Y~()92xrn-erAwR=~UQflwE23(zw)L ze>>JZU%_nNAh%@XUVf2v*(0@!w6ic`oo+2&16~dLvOckdI98=BJ{{yRLMLxsWf+&` zEd9oXc{slBcW+k;m(MQFuJh(?bX>_g^a|K zYZf;woLfdfx!DT&cbF4x6PJ=qC4WcvOw#Q5X>6W-+xJ6ak2e=SToh|eQ+#|kZc#Q} zaE#!ZZkkM*%CBm_cUAdlQ4{ynnzw})d54oZXc~~Ik%^YbaIKjvn6&NqFd#mo@6f!k z8*i8YH1TQvz0xt(BDy=RebV+^_ThW1t0{e>-)6A_O4-)Bn8M?1=8T98&AW{m^_d~E z4U!3RqLTCOXC3jVK5?X!h5eMr6b9`99|=hd!kt=Cydvu6=|<)D2E`qx8K=dh#fh+q z1Xp5lGseFeHy9t|%BI?;CMqj%)t{+7nPtsn**s=F?^&39ru?k^S=j$&Zgy@5`iWJ) zV3%Ohy{>zcMd(izGg8|J+wR+3u&?qFx9DNg@^9Ha;R->242^AQg!*Fr-uut32aak@ z-27NJP&W9jp$;cC-7bk;+-&6OxEfcu_xmt@{ zi|RKm!Lh-q=c18}ys@>K7uV(aX=*f*Lj)L<-gFXd225s+evbf8nDf@q( z5yKc_5|0^obh%#Z#g01V>)O!SBw`wN?3JaI^A`Qvo$sG}#xzIrbF!A~3)0s(m=w^5&F3_XG+oh=n$&}dmK>*$At5*Ok7F(U~!yC?eaJafBx${sby^1z!sOmX1 zM^jhE8Iqc7o;o|~=soRiv->m9J+L(db18ILC%sdD-&N_`!ncZvV5tzPA;~jBk=ws5 z-FMPlFMWS(_SEdwE8;r3y5n`hwc;=AG;ZW|5u%0(rHpsCsXxWUs^?B9zQE;9L9fwa zM%8_8tjVssc5$25Fl8Y~LQ|2g#Y`UG7mm&gY}YR3AJn%r84zPhf0u0^L6sTQeh z=gjw6%3EU|>)%CvlVN4PSsPiyR`N~3*1KMES+R(F%jjty4`mcVTSz($p7Aeg2lA(Db0FVow~MTawf<2tQN7lFO$=!< z)i~#Q+Q-nt(8VBUa#l6hqHTYFxx8v})p)wPu==Lif&cdN2BvqGL$bJ{%9P42k?T;5 zC8q6B?-`VVg+Wf}LipI2#YnX5L3?=9^5&iP1ix%bxm5I7y;;raQQDH|;bhvRqeYCx zK@)sFt!DctCVtVaaSH>Tzx0dp(%2Zu$Q70OAtUpK(qFV%-JQRo_05eR=ta7k7qug8 z=y%q+ttICj@WHl?bHk%WMQZ0AJa?Sx{V3Nc$nzaa^#d>dA~+p{#-l>&aD_To(UMg9-Byz!8o_a{=*7Qb7}jzs3R zdWn6V%RG30a041>Bkw6 zr}35DLH~ko)5vNP0cQg|@nJ}H+~>m;tsg1Sd5V04{4^!0N5&JYbH0^wFPkn-BR1xt zYEbww!&TXy^CWUBJ?e7*yrLmAB=lH#ucC7rx%jp9)85pY=K}s3mde6d6@7=@xsTv?=%St2e*x~^6Q-Ffb3l_)6 zAy+q~)Y_a=xp(txLXrfff1;{PaO9Zj{IbaVhA zyi5uZf>;3}yad7%9K`mId&0K=`+hE}s7y$$0vO`HO>}MFJ%F6)m0u z3kd%4?kE8NBU|Eaz->cSEiF7ZwD)mzMEkm6u+Yt{1iXUGOVi9302ujy5lG8`Zx=tER7K8_GsF>x_*9ymD!0#WpFa8fW(yYn|3zf$6H!D77>pwNJT0I>im zF^rEhR00NrLB%DZl9Hl$4^iJBG}bOq6z$9VSCIdXqvq&q@8jx)b;Y0|zv9}VFn(Ai z9-d!`{uzH=r(>Y&znRd!e{T!FLFg|FR63?Pvn1C{x|ZzucMDD#uJ|r3;*}D{!aU!#=n7z&|e$>k3#%a z<90of$H%8CxaEGS0mgiH#D%l|MEfWLPFE$?T4YqzSE0yk3I!gZvf3>x_UOasR=ID zlb&=hU!-l*we%H1=M*4NGQ zHie^}CeuxCl0w>E4_>?+I#;Sq@4Kwcd@s*ZL*g94@?~Rl-rU_6OwH1w)+5A3*g$(Y zvDQDi=3t(XCR{0i!O2hbz0JH!$!j5N2oAYAk_jf#)Hxdd9N_fDg&a0|ql6lV0&8gh zge~N_3*$s3c$MkE2sLWdyvV?qOPhT>7}i=2GLMzlGkC&`{7wshH{+X^HZ4=JDS@(H zPpZuVxfwq>W#V{mI9|-Nx?agZcLJN68|zn)O%M2{t6wCus~O^*u~*sG{H|q^a(Cmn zdTnk@g0%V3i7VZ7VPqHL-6=z9M8+J!^O_?0ky7QZVHU$%{$$SVZOLNI>ApsM4-4h2 zCf@ci^z|?)tv+~P5%?bs%Wif!?KDlP|TY}#vmvC={nGb7Byaj{&PN71=H>?^TaE9^n8?zxX)>h892 z4MN~QsGre3MfsvisVhVUmVeySq_>zQYDPUfX*3tFY!H$S<11$1<}0Por!zdOMEthP)Ep>cB!dq%)lEB)Z+vA)EiEH0wuPBjXw zS)d{u6B%{@)0X%{L`6*2c{XT4W_rAfMo3@~m@-FZxCi=z3Cjl!^hbcg6^f3E*Zd5I zq^jRk42iINc8N`5#5Dx<1KAwf!iFxl`XgFv(&@R7h~@6NpD^%8dYfw@!j!$Y5MnW~ z!P2%ZVogW4t5(ZxpT)(bQmGZnFw&AYjcm4sNo%qGmAAs!Z13?go5Lijg&w>M2osbU zBjFcNB*fvw>30Cyf|=7><##)u*h>Kh-&?b^b2v8BZOaz2xWf4m=&+h17*d?)&1`R)B5?)C=rvqoZ_J)!2X88uVttb0W?eYJFkFkk(|fHF=D&DQe~7Y_EhX zg(NoKR65za^!E!5DDn?IzI$_7Hto{qhdkgsnu7&%wt zIXule{hXQc{JBKmtc&j}Wrn9T(_c*_t@7MR)xOCxm9l2ev3 z&{d@8H|}jzLurErcmM?=(G%9rGjh=vS<0gV(1z8&<7jQ9(`LujXA>r~rhZUSi!p}j z8&DPAu0^=Z-AN;J$Ix^&q_^qX2^{pvlBh4F_vOs$QRb9Ot+<-!KL?dQPQ5H4PQHAh z&pVi=`-f#B{|yMMZN$E%{vDOQqTk5gm-tQEL^L>kJebIt646!Sq!QPknZ0xg9_$nk z*@^(D#Hoe+k2DOo$v)UJxB01r5tBx|8o6{SR|1hoE+aJvUBa$VC49K&AN(whULcfAM)~0!a{=G! zi=mD$`PwyCmuO1h`ILkEiqrmd@uwYAW8Kt+^Wmc=M46svDa3hSuW+NH`W6`2u@vaI z=wi3oK1ikAkg-T7rjBuA36FuQjaK`60#7OK44nEW|6#vE5p#gk>jDZxn&i0h7OJ3& z?3;(ndmH}2Cjsl9=SL^eS9LKz`;r&{k+L?y>#M#jIhfNGfXwNf z-&T=+0IUW(2kQk8;Cg|PKr5O8(CcSUQe1Vyh+TAY_FnVsUKbcUEh-w1M&Q4u04;T0 KwJH_c$NvQ)5P{R9@b3CU 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 PUlULjE#(?T>zDrrgaQon literal 0 HcmV?d00001 From 64bda4a10210f4745171f42f12bee51444de1374 Mon Sep 17 00:00:00 2001 From: Darshit Suratwala Date: Mon, 29 Apr 2024 16:17:26 +0530 Subject: [PATCH 06/24] feat: optimise docker image for self host setup --- .github/workflows/build-docker.yaml | 2 ++ .github/workflows/build-image.yaml | 55 ----------------------------- Dockerfile | 28 ++++++++++++++- docker-compose.yaml | 9 +++++ 4 files changed, 38 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/build-image.yaml diff --git a/.github/workflows/build-docker.yaml b/.github/workflows/build-docker.yaml index 38aeeed7..b1bce0dd 100644 --- a/.github/workflows/build-docker.yaml +++ b/.github/workflows/build-docker.yaml @@ -53,6 +53,7 @@ jobs: context: . file: ./Dockerfile platforms: linux/amd64,linux/arm64 + target: production push: true tags: | ${{ env.DOCKER_REGISTRY }}:${{ inputs.imageTag }} @@ -67,6 +68,7 @@ jobs: context: . file: ./Dockerfile platforms: linux/amd64,linux/arm64 + target: production push: true tags: ${{ env.DOCKER_REGISTRY }}:${{ inputs.imageTag }} labels: ${{ inputs.imageTag }} diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml deleted file mode 100644 index 157e5781..00000000 --- a/.github/workflows/build-image.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: "Build Docker Image" -run-name: "Version: ${{ inputs.imageTag }} --> Latest: ${{ inputs.isLatest }}" - -on: - workflow_dispatch: - inputs: - imageTag: - description: Release version - required: true - default: example - isLatest: - description: Is this the latest version? - type: boolean - required: true - default: false - -jobs: - docker-build: - runs-on: ubuntu-latest - env: - DOCKER_REGISTRY: scale3labs/langtrace-client - steps: - - name: Github Checkout - # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - - name: Log in to Docker Hub - # v3.1.0 - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push Docker image with latest tag - # v5.3.0 - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 - with: - context: . - file: ./Dockerfile - push: true - tags: | - ${{ env.DOCKER_REGISTRY }}:${{ inputs.imageTag }} - ${{ env.DOCKER_REGISTRY }}:latest - labels: ${{ inputs.imageTag }} - if: ${{ inputs.isLatest }} - - - name: Build and push Docker image without latest tag - # v5.3.0 - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ env.DOCKER_REGISTRY }}:${{ inputs.imageTag }} - if: ${{ !inputs.isLatest }} diff --git a/Dockerfile b/Dockerfile index 27888e67..b1ebebe1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Debian based node 21.6 image -FROM node:21.6-bookworm +FROM node:21.6-bookworm as development LABEL maintainer="Langtrace AI " LABEL version="1.0" @@ -17,3 +17,29 @@ RUN npm install EXPOSE 3000 CMD [ "/bin/sh", "-c", "npm run create-tables && npm run dev" ] + + +# Intermediate image for building the application +FROM development as builder + +WORKDIR /app + +RUN NEXT_PUBLIC_ENABLE_ADMIN_LOGIN=true npm run build + +# Final release image +FROM node:21.6-bookworm as production + +WORKDIR /app + +# Copy only the necessary files +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/package.json . +COPY --from=builder /app/public ./public + +# Install only production dependencies +RUN npm install --only=production --omit=dev + +CMD [ "/bin/sh", "-c", "npm run create-tables && npm start" ] + +EXPOSE 3000 diff --git a/docker-compose.yaml b/docker-compose.yaml index 078e6d4e..46ab0359 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,7 @@ services: build: context: . dockerfile: Dockerfile + target: development working_dir: /app env_file: - .env @@ -59,6 +60,14 @@ services: required: true command: clickhouse-client --host langtrace-clickhouse --query 'CREATE DATABASE IF NOT EXISTS $CLICK_HOUSE_DATABASE_NAME;' + langtrace-app-prod: + extends: + service: langtrace-app + build: + target: production + profiles: + - production + volumes: postgres-data: clickhouse-data: From a1025f71182c8d20bdc7696750dba89d029e366c Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 1 May 2024 12:46:46 -0400 Subject: [PATCH 07/24] adding api access to traces endpoint --- app/api/data/route.ts | 2 +- app/api/traces/route.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/api/data/route.ts b/app/api/data/route.ts index 6c4d3db7..89c907cc 100644 --- a/app/api/data/route.ts +++ b/app/api/data/route.ts @@ -106,7 +106,7 @@ export async function DELETE(req: NextRequest) { const data = await req.json(); const { id } = data; - const result = await prisma.data.delete({ + await prisma.data.delete({ where: { id, }, diff --git a/app/api/traces/route.ts b/app/api/traces/route.ts index 0c045993..76ade33d 100644 --- a/app/api/traces/route.ts +++ b/app/api/traces/route.ts @@ -1,4 +1,5 @@ import { authOptions } from "@/lib/auth/options"; +import prisma from "@/lib/prisma"; import { TraceService } from "@/lib/services/trace_service"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; @@ -8,11 +9,33 @@ export async function POST(req: NextRequest) { try { const session = await getServerSession(authOptions); - if (!session || !session.user) { - redirect("/login"); + const apiKey = req.headers.get("x-api-key"); + + if (!apiKey) { + if (!session || !session.user) { + redirect("/login"); + } } const { page, pageSize, projectId, filters } = await req.json(); + + const project = await prisma.project.findFirst({ + where: { + id: projectId, + }, + }); + + if (!project) { + return Response.json({ error: "No projects found" }, { status: 404 }); + } + + if (apiKey && project.apiKeyHash !== hashApiKey(apiKey)) { + return Response.json( + { error: "Unauthorized. Invalid API key" }, + { status: 401 } + ); + } + const traceService = new TraceService(); const traces = await traceService.GetTracesInProjectPaginated( projectId, From bc90bbde789bc74582486dd892214ea302edfab4 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 2 May 2024 12:30:12 -0400 Subject: [PATCH 08/24] clean up --- app/api/traces/route.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/api/traces/route.ts b/app/api/traces/route.ts index 76ade33d..1c881b56 100644 --- a/app/api/traces/route.ts +++ b/app/api/traces/route.ts @@ -1,6 +1,7 @@ import { authOptions } from "@/lib/auth/options"; import prisma from "@/lib/prisma"; import { TraceService } from "@/lib/services/trace_service"; +import { hashApiKey } from "@/lib/utils"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { NextRequest, NextResponse } from "next/server"; @@ -26,11 +27,11 @@ export async function POST(req: NextRequest) { }); if (!project) { - return Response.json({ error: "No projects found" }, { status: 404 }); + return NextResponse.json({ error: "No projects found" }, { status: 404 }); } if (apiKey && project.apiKeyHash !== hashApiKey(apiKey)) { - return Response.json( + return NextResponse.json( { error: "Unauthorized. Invalid API key" }, { status: 401 } ); From 1426245338c2e548df5c473803b1913b3f3175fb Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 2 May 2024 12:49:41 -0400 Subject: [PATCH 09/24] refactor --- app/api/traces/route.ts | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/api/traces/route.ts b/app/api/traces/route.ts index 1c881b56..64034c71 100644 --- a/app/api/traces/route.ts +++ b/app/api/traces/route.ts @@ -9,34 +9,34 @@ import { NextRequest, NextResponse } from "next/server"; export async function POST(req: NextRequest) { try { const session = await getServerSession(authOptions); - const apiKey = req.headers.get("x-api-key"); - - if (!apiKey) { - if (!session || !session.user) { + const { page, pageSize, projectId, filters } = await req.json(); + if (!session || !session.user) { + if (apiKey) { + const project = await prisma.project.findFirst({ + where: { + id: projectId, + }, + }); + + if (!project) { + return NextResponse.json( + { error: "No projects found" }, + { status: 404 } + ); + } + + if (apiKey && project.apiKeyHash !== hashApiKey(apiKey)) { + return NextResponse.json( + { error: "Unauthorized. Invalid API key" }, + { status: 401 } + ); + } + } else { redirect("/login"); } } - const { page, pageSize, projectId, filters } = await req.json(); - - const project = await prisma.project.findFirst({ - where: { - id: projectId, - }, - }); - - if (!project) { - return NextResponse.json({ error: "No projects found" }, { status: 404 }); - } - - if (apiKey && project.apiKeyHash !== hashApiKey(apiKey)) { - return NextResponse.json( - { error: "Unauthorized. Invalid API key" }, - { status: 401 } - ); - } - const traceService = new TraceService(); const traces = await traceService.GetTracesInProjectPaginated( projectId, From ea5cd7b37e1a923443b3a45260cd93808ba633ac Mon Sep 17 00:00:00 2001 From: darshit-s3 <119623510+darshit-s3@users.noreply.github.com> Date: Sat, 4 May 2024 11:49:32 +0530 Subject: [PATCH 10/24] feat: add clickhouse db create on app start (#79) --- .env | 4 +- Dockerfile | 5 +- docker-compose.yaml | 19 +- package-lock.json | 409 +++++++++++++++++++++++++++++++- package.json | 6 +- scripts/create-clickhouse-db.ts | 31 +++ 6 files changed, 449 insertions(+), 25 deletions(-) create mode 100644 scripts/create-clickhouse-db.ts diff --git a/.env b/.env index d67882e9..367ef82d 100644 --- a/.env +++ b/.env @@ -21,9 +21,9 @@ NEXTAUTH_URL_INTERNAL="${NEXT_PUBLIC_HOST}" CLICK_HOUSE_HOST="http://langtrace-clickhouse:8123" CLICK_HOUSE_USER="default" CLICK_HOUSE_PASSWORD="" -CLICK_HOUSE_DATABASE_NAME="langtrace_dev" +CLICK_HOUSE_DATABASE_NAME="langtrace_traces" # Admin login ADMIN_EMAIL="admin@langtrace.ai" ADMIN_PASSWORD="langtraceadminpw" -NEXT_PUBLIC_ENABLE_ADMIN_LOGIN="true" \ No newline at end of file +NEXT_PUBLIC_ENABLE_ADMIN_LOGIN="true" diff --git a/Dockerfile b/Dockerfile index b1ebebe1..c7cdf011 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN npm install EXPOSE 3000 -CMD [ "/bin/sh", "-c", "npm run create-tables && npm run dev" ] +CMD [ "/bin/sh", "-c", "npm run dev" ] # Intermediate image for building the application @@ -36,10 +36,11 @@ COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/.next ./.next COPY --from=builder /app/package.json . COPY --from=builder /app/public ./public +COPY --from=builder /app/scripts ./scripts # Install only production dependencies RUN npm install --only=production --omit=dev -CMD [ "/bin/sh", "-c", "npm run create-tables && npm start" ] +CMD [ "/bin/sh", "-c", "npm start" ] EXPOSE 3000 diff --git a/docker-compose.yaml b/docker-compose.yaml index 46ab0359..5e3d6e3d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.8" - services: langtrace-app: container_name: langtrace @@ -13,15 +11,17 @@ services: - .env ports: - "3000:3000" + command: npm run dev # Uncmment this for development # volumes: # - .:/app + restart: on-failure:5 depends_on: postgres-db: condition: service_started required: true - clickhouse-init: - condition: service_completed_successfully + clickhouse-db: + condition: service_healthy required: true postgres-db: @@ -49,17 +49,6 @@ services: volumes: - clickhouse-data:/var/lib/clickhouse - clickhouse-init: - container_name: langtrace-clickhouse-init - image: clickhouse/clickhouse-server:23.3.20.27-alpine - env_file: - - .env - depends_on: - clickhouse-db: - condition: service_healthy - required: true - command: clickhouse-client --host langtrace-clickhouse --query 'CREATE DATABASE IF NOT EXISTS $CLICK_HOUSE_DATABASE_NAME;' - langtrace-app-prod: extends: service: langtrace-app diff --git a/package-lock.json b/package-lock.json index c936e48f..92389943 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "stream-to-array": "^2.3.0", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.8.2", "zod": "^3.22.4" }, "devDependencies": { @@ -949,6 +950,351 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -6055,6 +6401,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -6948,10 +7331,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", - "dev": true, + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -11576,7 +11958,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -12654,6 +13035,24 @@ "resolved": "https://registry.npmjs.org/tsv/-/tsv-0.2.0.tgz", "integrity": "sha512-GG6xbOP85giXXom0dS6z9uyDsxktznjpa1AuDlPrIXDqDnbhjr9Vk6Us8iz6U1nENL4CPS2jZDvIjEdaZsmc4Q==" }, + "node_modules/tsx": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.9.0.tgz", + "integrity": "sha512-UY0UUhDPL6MkqkZU4xTEjEBOLfV+RIt4xeeJ1qwK73xai4/zveG+X6+tieILa7rjtegUW2LE4p7fw7gAoLuytA==", + "dependencies": { + "esbuild": "~0.20.2", + "get-tsconfig": "^4.7.3" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", diff --git a/package.json b/package.json index 11557f41..8b78c400 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,13 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", "build": "prisma generate && next build", "create-tables": "npx prisma db push", "seed-db": "npx prisma db seed", + "create-clickhouse-db": "npx tsx scripts/create-clickhouse-db.ts", + "predev": "npm run create-tables && npm run create-clickhouse-db", + "dev": "next dev", + "prestart": "npm run create-tables && npm run create-clickhouse-db", "start": "next start", "lint": "next lint" }, @@ -85,6 +88,7 @@ "stream-to-array": "^2.3.0", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.8.2", "zod": "^3.22.4" }, "devDependencies": { diff --git a/scripts/create-clickhouse-db.ts b/scripts/create-clickhouse-db.ts new file mode 100644 index 00000000..e03aa219 --- /dev/null +++ b/scripts/create-clickhouse-db.ts @@ -0,0 +1,31 @@ +import { createClient } from "@clickhouse/client"; + +import { loadEnvConfig } from "@next/env"; +loadEnvConfig(process.cwd()); + +const chClient = createClient({ + database: "default", + host: process.env.CLICK_HOUSE_HOST, + username: process.env.CLICK_HOUSE_USER, + password: process.env.CLICK_HOUSE_PASSWORD, + compression: { + response: true, + }, + clickhouse_settings: { + async_insert: 1, + wait_for_async_insert: 1, + }, +}); + +console.log("Creating Clickhouse DB if not exists..."); +chClient + .query({ + query: `CREATE DATABASE IF NOT EXISTS ${process.env.CLICK_HOUSE_DATABASE_NAME}`, + }) + .then((res) => { + console.log(res.query_id); + }) + .catch((err) => { + console.error(err); + process.exit(1); + }); From 34875e2a01e34510e6fa033bae64dfab6029af83 Mon Sep 17 00:00:00 2001 From: darshit-s3 <119623510+darshit-s3@users.noreply.github.com> Date: Tue, 7 May 2024 20:25:50 +0530 Subject: [PATCH 11/24] docs: add railway deploy, fix sdk badges (#81) --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b2c6a731..d8f2e5bc 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,17 @@ ## Open Source & Open Telemetry(OTEL) Observability for LLM applications -![Static Badge](https://img.shields.io/badge/License-AGPL--3.0-blue) ![Static Badge](https://img.shields.io/badge/npm_@langtrase/typescript--sdk-1.2.9-green) ![Static Badge](https://img.shields.io/badge/pip_langtrace--python--sdk-1.2.8-green) ![Static Badge](https://img.shields.io/badge/Development_status-Active-green) +![Static Badge](https://img.shields.io/badge/License-AGPL--3.0-blue) +![NPM Version](https://img.shields.io/npm/v/%40langtrase%2Ftypescript-sdk?style=flat&logo=npm&label=%40langtrase%2Ftypescript-sdk&color=green&link=https%3A%2F%2Fgithub.com%2FScale3-Labs%2Flangtrace-typescript-sdk) +![PyPI - Version](https://img.shields.io/pypi/v/langtrace-python-sdk?style=flat&logo=python&label=langtrace-python-sdk&color=green&link=https%3A%2F%2Fgithub.com%2FScale3-Labs%2Flangtrace-python-sdk) +![Static Badge](https://img.shields.io/badge/Development_status-Active-green) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/yZGbfC?referralCode=MA2S9H) --- Langtrace is an open source observability software which lets you capture, debug and analyze traces and metrics from all your applications that leverages LLM APIs, Vector Databases and LLM based Frameworks. -![image](public/eval.png) +![image](https://github.com/Scale3-Labs/langtrace/assets/105607645/6825158c-39bb-4270-b1f9-446c36c066ee) ## Open Telemetry Support @@ -72,6 +76,9 @@ To run the Langtrace locally, you have to run three services: - Postgres database - Clickhouse database +> [!IMPORTANT] +> Checkout [documentation](https://docs.langtrace.ai/hosting/overview) for various deployment options and configurations. + Requirements: - Docker @@ -93,7 +100,7 @@ The application will be available at `http://localhost:3000`. > if you wish to build the docker image locally and use it, run the docker compose up command with the `--build` flag. > [!TIP] -> to manually pull the docker image from docker hub, run the following command: +> to manually pull the docker image from [docker hub](https://hub.docker.com/r/scale3labs/langtrace-client/tags), run the following command: > > ```bash > docker pull scale3labs/langtrace-client:latest @@ -192,6 +199,7 @@ Either you **update the docker compose version** OR **remove the depends_on prop If clickhouse server is not starting, it is likely that the port 8123 is already in use. You can change the port in the docker-compose file. +
Install the langtrace SDK in your application by following the same instructions under the Langtrace Cloud section above for sending traces to your self hosted setup. --- @@ -223,6 +231,12 @@ Langtrace automatically captures traces from the following vendors: --- +## Langtrace System Architecture + +![image](https://github.com/Scale3-Labs/langtrace/assets/105607645/eae180dd-ebf7-4792-b076-23f75d3734a8) + +--- + ## Feature Requests and Issues - To request for features, head over [here to start a discussion](https://github.com/Scale3-Labs/langtrace/discussions/categories/feature-requests). From 4551d7eb33a22b4ba94be8eb1e20d620c6e0a567 Mon Sep 17 00:00:00 2001 From: Rohit Kadhe Date: Tue, 7 May 2024 09:23:39 -0600 Subject: [PATCH 12/24] untrack .env --- .env | 29 ----------------------------- .gitignore | 2 ++ 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 367ef82d..00000000 --- a/.env +++ /dev/null @@ -1,29 +0,0 @@ -# Postgres Variables -POSTGRES_HOST="langtrace-postgres:5432" -POSTGRES_USER="ltuser" -POSTGRES_PASSWORD="ltpasswd" -POSTGRES_DATABASE="langtrace" -POSTGRES_DB="${POSTGRES_DATABASE}" -POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DATABASE}" -POSTGRES_PRISMA_URL="${POSTGRES_URL}?pgbouncer=true&connect_timeout=15" -POSTGRES_URL_NO_SSL="${POSTGRES_URL}" -POSTGRES_URL_NON_POOLING="${POSTGRES_URL}" - -# Application Variables -NEXT_PUBLIC_APP_NAME="Langtrace" -NEXT_PUBLIC_ENVIRONMENT="development" -NEXT_PUBLIC_HOST="http://localhost:3000" -NEXTAUTH_SECRET="difficultsecret" -NEXTAUTH_URL="${NEXT_PUBLIC_HOST}" -NEXTAUTH_URL_INTERNAL="${NEXT_PUBLIC_HOST}" - -# Clickhouse Variables -CLICK_HOUSE_HOST="http://langtrace-clickhouse:8123" -CLICK_HOUSE_USER="default" -CLICK_HOUSE_PASSWORD="" -CLICK_HOUSE_DATABASE_NAME="langtrace_traces" - -# Admin login -ADMIN_EMAIL="admin@langtrace.ai" -ADMIN_PASSWORD="langtraceadminpw" -NEXT_PUBLIC_ENABLE_ADMIN_LOGIN="true" diff --git a/.gitignore b/.gitignore index f8447f42..53d2a8c0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ next-env.d.ts cli/config*.ini cli/build/* cli/dist/* + +.env \ No newline at end of file From c7b05cad8a009b9cf3bc22c0161ea6f87a07a630 Mon Sep 17 00:00:00 2001 From: Rohit Kadhe Date: Tue, 7 May 2024 09:25:31 -0600 Subject: [PATCH 13/24] Revert "untrack .env" This reverts commit 4551d7eb33a22b4ba94be8eb1e20d620c6e0a567. --- .env | 29 +++++++++++++++++++++++++++++ .gitignore | 2 -- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 00000000..367ef82d --- /dev/null +++ b/.env @@ -0,0 +1,29 @@ +# Postgres Variables +POSTGRES_HOST="langtrace-postgres:5432" +POSTGRES_USER="ltuser" +POSTGRES_PASSWORD="ltpasswd" +POSTGRES_DATABASE="langtrace" +POSTGRES_DB="${POSTGRES_DATABASE}" +POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DATABASE}" +POSTGRES_PRISMA_URL="${POSTGRES_URL}?pgbouncer=true&connect_timeout=15" +POSTGRES_URL_NO_SSL="${POSTGRES_URL}" +POSTGRES_URL_NON_POOLING="${POSTGRES_URL}" + +# Application Variables +NEXT_PUBLIC_APP_NAME="Langtrace" +NEXT_PUBLIC_ENVIRONMENT="development" +NEXT_PUBLIC_HOST="http://localhost:3000" +NEXTAUTH_SECRET="difficultsecret" +NEXTAUTH_URL="${NEXT_PUBLIC_HOST}" +NEXTAUTH_URL_INTERNAL="${NEXT_PUBLIC_HOST}" + +# Clickhouse Variables +CLICK_HOUSE_HOST="http://langtrace-clickhouse:8123" +CLICK_HOUSE_USER="default" +CLICK_HOUSE_PASSWORD="" +CLICK_HOUSE_DATABASE_NAME="langtrace_traces" + +# Admin login +ADMIN_EMAIL="admin@langtrace.ai" +ADMIN_PASSWORD="langtraceadminpw" +NEXT_PUBLIC_ENABLE_ADMIN_LOGIN="true" diff --git a/.gitignore b/.gitignore index 53d2a8c0..f8447f42 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,3 @@ next-env.d.ts cli/config*.ini cli/build/* cli/dist/* - -.env \ No newline at end of file From 096ee2e9b38c24662d910411eac81240b8169c84 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman <105607645+karthikscale3@users.noreply.github.com> Date: Tue, 7 May 2024 09:50:10 -0700 Subject: [PATCH 14/24] Playground and Prompt Management (#83) * Pagination bug * Bug fix * Playground - basic implementation * Playground - streaming and nonstreaming * Move playground inside project * API key flow * Api key * Playground refactor * Add chat hookup * anthropic streaming support * Bug fixes to openai playground * Anthropic bugfixes * Anthropic bugfix * Cohere first iteration * Cohere role fixes * Cohere api fix * Parallel running * Playground cost calculation non streaming * playground - streaming token calculation * latency and cost * Support for Groq * Add model name * Prompt management views * Remove current promptset flow * Prompt management API hooks * Prompt registry final * Playground bugfixes * Bug fix playground * Rearrange project nav * Fix playground * Fix prompts * Bugfixes * Minor fix * Prompt versioning bugfix * Bugfix --- .../project/[project_id]/datasets/page.tsx | 4 +- .../promptset/[promptset_id]/page.tsx | 183 - .../project/[project_id]/playground/page.tsx | 101 + .../[project_id]/prompts/[prompt_id]/page.tsx | 247 + .../[project_id]/prompts/page-client.tsx | 36 +- .../project/[project_id]/prompts/page.tsx | 4 +- .../prompts/prompt-management.tsx | 51 +- app/(protected)/projects/page-client.tsx | 10 +- app/(protected)/settings/keys/page-client.tsx | 89 + app/(protected)/settings/keys/page.tsx | 23 + app/api/chat/anthropic/route.ts | 37 + app/api/chat/cohere/route.ts | 40 + app/api/chat/groq/route.ts | 37 + app/api/chat/openai/route.ts | 37 + app/api/prompt/route.ts | 166 +- app/api/promptdata/route.ts | 123 - app/api/promptset/route.ts | 15 +- app/api/span-prompt/route.ts | 66 + components/evaluations/evaluation-row.tsx | 2 - components/evaluations/evaluation-table.tsx | 2 - components/playground/chat-handlers.ts | 301 + components/playground/common.tsx | 169 + components/playground/llmchat.tsx | 561 + components/playground/model-dropdown.tsx | 86 + components/playground/settings-sheet.tsx | 2532 +++ components/project/dataset/create-data.tsx | 128 +- components/project/dataset/create.tsx | 26 +- components/project/dataset/data-set.tsx | 8 +- components/project/dataset/edit-data.tsx | 232 +- components/project/dataset/edit.tsx | 46 +- components/project/dataset/parent.tsx | 39 - components/settings/tabs.tsx | 5 + components/shared/add-api-key.tsx | 174 + components/shared/add-to-promptset.tsx | 191 - components/shared/code-block.tsx | 18 + components/shared/create-prompt-dialog.tsx | 369 + components/shared/diff-view.tsx | 49 + components/shared/header.tsx | 18 +- components/shared/llm-picker.tsx | 72 + components/shared/nav.tsx | 17 +- components/ui/sheet.tsx | 140 + lib/constants.ts | 53 +- lib/types/playground_types.ts | 261 + lib/utils.ts | 21 + package-lock.json | 13905 ---------------- package.json | 12 +- .../migration.sql | 5 + .../migration.sql | 11 + .../migration.sql | 9 + prisma/schema.prisma | 22 +- public/spinner-dark.svg | 26 + public/spinner-light.svg | 26 + 52 files changed, 5821 insertions(+), 14984 deletions(-) delete mode 100644 app/(protected)/project/[project_id]/datasets/promptset/[promptset_id]/page.tsx create mode 100644 app/(protected)/project/[project_id]/playground/page.tsx create mode 100644 app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx rename components/project/dataset/prompt-set.tsx => app/(protected)/project/[project_id]/prompts/prompt-management.tsx (63%) create mode 100644 app/(protected)/settings/keys/page-client.tsx create mode 100644 app/(protected)/settings/keys/page.tsx create mode 100644 app/api/chat/anthropic/route.ts create mode 100644 app/api/chat/cohere/route.ts create mode 100644 app/api/chat/groq/route.ts create mode 100644 app/api/chat/openai/route.ts delete mode 100644 app/api/promptdata/route.ts create mode 100644 app/api/span-prompt/route.ts create mode 100644 components/playground/chat-handlers.ts create mode 100644 components/playground/common.tsx create mode 100644 components/playground/llmchat.tsx create mode 100644 components/playground/model-dropdown.tsx create mode 100644 components/playground/settings-sheet.tsx delete mode 100644 components/project/dataset/parent.tsx create mode 100644 components/shared/add-api-key.tsx delete mode 100644 components/shared/add-to-promptset.tsx create mode 100644 components/shared/code-block.tsx create mode 100644 components/shared/create-prompt-dialog.tsx create mode 100644 components/shared/diff-view.tsx create mode 100644 components/shared/llm-picker.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 lib/types/playground_types.ts delete mode 100644 package-lock.json create mode 100644 prisma/migrations/20240505002132_prompt_management/migration.sql create mode 100644 prisma/migrations/20240505011630_prompt_management_v2/migration.sql create mode 100644 prisma/migrations/20240506153246_prompt_management_mark_live/migration.sql create mode 100644 public/spinner-dark.svg create mode 100644 public/spinner-light.svg diff --git a/app/(protected)/project/[project_id]/datasets/page.tsx b/app/(protected)/project/[project_id]/datasets/page.tsx index ca6b389b..2cc177a3 100644 --- a/app/(protected)/project/[project_id]/datasets/page.tsx +++ b/app/(protected)/project/[project_id]/datasets/page.tsx @@ -1,4 +1,4 @@ -import Parent from "@/components/project/dataset/parent"; +import DataSet from "@/components/project/dataset/data-set"; import { authOptions } from "@/lib/auth/options"; import { Metadata } from "next"; import { getServerSession } from "next-auth"; @@ -18,7 +18,7 @@ export default async function Page() { return ( <> - + ); } diff --git a/app/(protected)/project/[project_id]/datasets/promptset/[promptset_id]/page.tsx b/app/(protected)/project/[project_id]/datasets/promptset/[promptset_id]/page.tsx deleted file mode 100644 index 5390ee95..00000000 --- a/app/(protected)/project/[project_id]/datasets/promptset/[promptset_id]/page.tsx +++ /dev/null @@ -1,183 +0,0 @@ -"use client"; - -import { CreatePrompt } from "@/components/project/dataset/create-data"; -import { EditPrompt } from "@/components/project/dataset/edit-data"; -import { Spinner } from "@/components/shared/spinner"; -import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; -import { Skeleton } from "@/components/ui/skeleton"; -import { PAGE_SIZE } from "@/lib/constants"; -import { Prompt } from "@prisma/client"; -import { ChevronLeft } from "lucide-react"; -import { useParams } from "next/navigation"; -import { useState } from "react"; -import { useBottomScrollListener } from "react-bottom-scroll-listener"; -import { useQuery } from "react-query"; -import { toast } from "sonner"; - -export default function Promptset() { - const promptset_id = useParams()?.promptset_id as string; - const [page, setPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); - const [showLoader, setShowLoader] = useState(false); - const [currentData, setCurrentData] = useState([]); - - useBottomScrollListener(() => { - if (fetchPromptset.isRefetching) { - return; - } - if (page <= totalPages) { - setShowLoader(true); - fetchPromptset.refetch(); - } - }); - - const fetchPromptset = useQuery({ - queryKey: [promptset_id], - queryFn: async () => { - const response = await fetch( - `/api/promptset?promptset_id=${promptset_id}&page=${page}&pageSize=${PAGE_SIZE}` - ); - if (!response.ok) { - const error = await response.json(); - throw new Error(error?.message || "Failed to fetch promptset"); - } - const result = await response.json(); - return result; - }, - onSuccess: (data) => { - // Get the newly fetched data and metadata - const newData: Prompt[] = data?.promptsets?.Prompt || []; - const metadata = data?.metadata || {}; - - // Update the total pages and current page number - setTotalPages(parseInt(metadata?.total_pages) || 1); - if (parseInt(metadata?.page) <= parseInt(metadata?.total_pages)) { - setPage(parseInt(metadata?.page) + 1); - } - - // Merge the new data with the existing data - if (currentData.length > 0) { - const updatedData = [...currentData, ...newData]; - - // Remove duplicates - const uniqueData = updatedData.filter( - (v: any, i: number, a: any) => - a.findIndex((t: any) => t.id === v.id) === i - ); - - setCurrentData(uniqueData); - } else { - setCurrentData(newData); - } - setShowLoader(false); - }, - onError: (error) => { - setShowLoader(false); - toast.error("Failed to fetch promptset", { - description: error instanceof Error ? error.message : String(error), - }); - }, - }); - - if (fetchPromptset.isLoading || !fetchPromptset.data || !currentData) { - return ; - } else { - return ( -
-
- - -
-
-
-

Created at

-

Value

-

Note

-

-
- {!fetchPromptset.isLoading && currentData.length === 0 && ( -
-

- No prompts found in this promptset -

-
- )} - {!fetchPromptset.isLoading && - currentData.length > 0 && - currentData.map((prompt: any, i: number) => { - return ( -
-
-

{prompt.createdAt}

-

{prompt.value}

-

{prompt.note}

-
- -
-
- -
- ); - })} - {showLoader && ( -
- -
- )} -
-
- ); - } -} - -function PageSkeleton() { - return ( -
-
- - -
-
-
-

Created at

-

Value

-

Note

-

-
- {Array.from({ length: 3 }).map((_, index) => ( - - ))} -
-
- ); -} - -function PromptsetRowSkeleton() { - return ( -
-
-
- -
-
- -
-
- -
-
- -
- ); -} diff --git a/app/(protected)/project/[project_id]/playground/page.tsx b/app/(protected)/project/[project_id]/playground/page.tsx new file mode 100644 index 00000000..56731b7d --- /dev/null +++ b/app/(protected)/project/[project_id]/playground/page.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { AddLLMChat } from "@/components/playground/common"; +import LLMChat from "@/components/playground/llmchat"; +import { + AnthropicModel, + AnthropicSettings, + ChatInterface, + CohereSettings, + GroqSettings, + OpenAIChatInterface, + OpenAIModel, + OpenAISettings, +} from "@/lib/types/playground_types"; +import Link from "next/link"; +import { useState } from "react"; +import { v4 as uuidv4 } from "uuid"; + +export default function Page() { + const [llms, setLLMs] = useState([]); + + const handleRemove = (id: string) => { + setLLMs((currentLLMs) => currentLLMs.filter((llm) => llm.id !== id)); + }; + + const handleAdd = (vendor: string) => { + if (vendor === "openai") { + const settings: OpenAISettings = { + messages: [], + model: "gpt-3.5-turbo" as OpenAIModel, + }; + const openaiChat: OpenAIChatInterface = { + id: uuidv4(), + vendor: "openai", + settings: settings, + }; + setLLMs((currentLLMs) => [...currentLLMs, openaiChat]); + } else if (vendor === "anthropic") { + const settings: AnthropicSettings = { + messages: [], + model: "claude-3-opus-20240229" as AnthropicModel, + maxTokens: 100, + }; + const anthropicChat: ChatInterface = { + id: uuidv4(), + vendor: "anthropic", + settings: settings, + }; + setLLMs((currentLLMs) => [...currentLLMs, anthropicChat]); + } else if (vendor === "cohere") { + const settings: CohereSettings = { + messages: [], + model: "command-r-plus", + }; + const cohereChat: ChatInterface = { + id: uuidv4(), + vendor: "cohere", + settings: settings, + }; + setLLMs((currentLLMs) => [...currentLLMs, cohereChat]); + } else if (vendor === "groq") { + const settings: GroqSettings = { + messages: [], + model: "llama3-8b-8192", + }; + const cohereChat: ChatInterface = { + id: uuidv4(), + vendor: "groq", + settings: settings, + }; + setLLMs((currentLLMs) => [...currentLLMs, cohereChat]); + } + }; + + return ( +
+ + Note: Don't forget to add your LLM provider API keys in the{" "} + + settings page. + + +
+ {llms.map((llm: ChatInterface) => ( + { + const newLLMs = llms.map((l) => + l.id === llm.id ? updatedLLM : l + ); + setLLMs(newLLMs); + }} + onRemove={() => handleRemove(llm.id)} + /> + ))} + handleAdd(vendor)} /> +
+
+ ); +} diff --git a/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx b/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx new file mode 100644 index 00000000..dd58727b --- /dev/null +++ b/app/(protected)/project/[project_id]/prompts/[prompt_id]/page.tsx @@ -0,0 +1,247 @@ +"use client"; +import CreatePromptDialog from "@/components/shared/create-prompt-dialog"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/lib/utils"; +import { Prompt } from "@prisma/client"; +import CodeEditor from "@uiw/react-textarea-code-editor"; +import { ChevronLeft } from "lucide-react"; +import { useParams, useRouter } from "next/navigation"; +import { useState } from "react"; +import { useQuery, useQueryClient } from "react-query"; +import { toast } from "sonner"; + +export default function Prompt() { + const promptsetId = useParams()?.prompt_id as string; + const router = useRouter(); + const [prompts, setPrompts] = useState([]); + const [selectedPrompt, setSelectedPrompt] = useState(); + const [live, setLive] = useState(false); + const queryClient = useQueryClient(); + + const { isLoading: promptsLoading, error: promptsError } = useQuery({ + queryKey: ["fetch-prompts-query", promptsetId], + queryFn: async () => { + const response = await fetch( + `/api/promptset?promptset_id=${promptsetId}` + ); + if (!response.ok) { + const error = await response.json(); + throw new Error(error?.message || "Failed to fetch tests"); + } + const result = await response.json(); + setPrompts(result?.promptsets?.prompts || []); + if (result?.promptsets?.prompts.length > 0 && !selectedPrompt) { + setSelectedPrompt(result?.promptsets?.prompts[0]); + setLive(result?.promptsets?.prompts[0].live); + } + return result; + }, + onError: (error) => { + toast.error("Failed to fetch prompts", { + description: error instanceof Error ? error.message : String(error), + }); + }, + }); + + if (promptsLoading) return ; + + if (!selectedPrompt) + return ( +
+ +
+

Create your first prompt

+

+ Start by creating the first version of your prompt. Once created, + you can test it in the playground with different models and model + settings and continue to iterate and add more versions to the + prompt. +

+ +
+
+ ); + else + return ( +
+
+ + {prompts.length > 0 ? ( + + ) : ( + + )} +
+
+
+ {prompts.map((prompt: Prompt, i) => ( +
{ + setSelectedPrompt(prompt); + setLive(prompt.live); + }} + className={cn( + "flex gap-4 items-start w-full rounded-md p-2 hover:bg-muted cursor-pointer", + selectedPrompt.id === prompt.id ? "bg-muted" : "" + )} + key={prompt.id} + > +
+
+ v{prompt.version} +
+ +
+
+ {prompt.live && ( +

+ Live +

+ )} +

+ {prompt.note || `Version ${prompt.version}`} +

+
+
+ ))} +
+
+
+ +

+ {selectedPrompt.value} +

+
+
+ +
+ {selectedPrompt.variables.map((variable: string) => ( + + {variable} + + ))} +
+
+
+ +

+ {selectedPrompt.model ?? "None"} +

+
+
+ + +
+
+ +
+ { + setLive(checked as boolean); + try { + const payload = { + ...selectedPrompt, + live: checked, + }; + await fetch("/api/prompt", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + await queryClient.invalidateQueries({ + queryKey: ["fetch-prompts-query", promptsetId], + }); + toast.success( + checked + ? "This prompt is now live" + : "This prompt is no longer live. Make sure to make another prompt live" + ); + } catch (error) { + toast.error("Failed to make prompt live", { + description: + error instanceof Error + ? error.message + : String(error), + }); + } + }} + /> +

+ Make this version of the prompt live +

+
+
+
+
+
+ ); +} + +function PageLoading() { + return ( +
+
+ + +
+
+ + +
+
+ ); +} diff --git a/app/(protected)/project/[project_id]/prompts/page-client.tsx b/app/(protected)/project/[project_id]/prompts/page-client.tsx index 94815450..8b4eafcf 100644 --- a/app/(protected)/project/[project_id]/prompts/page-client.tsx +++ b/app/(protected)/project/[project_id]/prompts/page-client.tsx @@ -1,6 +1,5 @@ "use client"; -import { AddtoPromptset } from "@/components/shared/add-to-promptset"; import { Spinner } from "@/components/shared/spinner"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -8,7 +7,6 @@ import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { PAGE_SIZE } from "@/lib/constants"; import { extractSystemPromptFromLlmInputs } from "@/lib/utils"; -import { CheckCircledIcon } from "@radix-ui/react-icons"; import { ChevronDown, ChevronRight, RabbitIcon } from "lucide-react"; import { useParams } from "next/navigation"; import { useState } from "react"; @@ -42,7 +40,7 @@ export default function PageClient({ email }: { email: string }) { queryKey: [`fetch-prompts-${projectId}-query`], queryFn: async () => { const response = await fetch( - `/api/prompt?projectId=${projectId}&page=${page}&pageSize=${PAGE_SIZE}` + `/api/span-prompt?projectId=${projectId}&page=${page}&pageSize=${PAGE_SIZE}` ); if (!response.ok) { const error = await response.json(); @@ -130,9 +128,6 @@ export default function PageClient({ email }: { email: string }) { return (
-
- -

These prompts are automatically captured from your traces. The accuracy of these prompts are calculated based on the evaluation done @@ -145,7 +140,6 @@ export default function PageClient({ email }: { email: string }) {

Interactions

Prompt

Accuracy

-

Added to Dataset

{dedupedPrompts.map((prompt: any, i: number) => { return ( @@ -187,26 +181,6 @@ const PromptRow = ({ }) => { const [collapsed, setCollapsed] = useState(true); const [accuracy, setAccuracy] = useState(0); - const [addedToPromptset, setAddedToPromptset] = useState(false); - - useQuery({ - queryKey: [`fetch-promptdata-query-${prompt.span_id}`], - queryFn: async () => { - const response = await fetch(`/api/promptdata?spanId=${prompt.span_id}`); - if (!response.ok) { - const error = await response.json(); - throw new Error(error?.message || "Failed to fetch prompt data"); - } - const result = await response.json(); - setAddedToPromptset(result.data.length > 0); - return result; - }, - onError: (error) => { - toast.error("Failed to fetch prompt data", { - description: error instanceof Error ? error.message : String(error), - }); - }, - }); // Get the evaluation for this prompt's content const attributes = prompt.attributes ? JSON.parse(prompt.attributes) : {}; @@ -302,11 +276,6 @@ const PromptRow = ({

{accuracy?.toFixed(2)}%

- {addedToPromptset ? ( - - ) : ( - "" - )}
{!collapsed && (
@@ -325,9 +294,6 @@ const PromptRow = ({ function PageLoading() { return (
-
- -

These prompts are automatically captured from your traces. The accuracy of these prompts are calculated based on the evaluation done in the diff --git a/app/(protected)/project/[project_id]/prompts/page.tsx b/app/(protected)/project/[project_id]/prompts/page.tsx index f6698bb8..0f0d9dca 100644 --- a/app/(protected)/project/[project_id]/prompts/page.tsx +++ b/app/(protected)/project/[project_id]/prompts/page.tsx @@ -2,7 +2,7 @@ import { authOptions } from "@/lib/auth/options"; import { Metadata } from "next"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; -import PageClient from "./page-client"; +import PromptManagement from "./prompt-management"; export const metadata: Metadata = { title: "Langtrace | Prompts", @@ -18,7 +18,7 @@ export default async function Page() { return ( <> - + ); } diff --git a/components/project/dataset/prompt-set.tsx b/app/(protected)/project/[project_id]/prompts/prompt-management.tsx similarity index 63% rename from components/project/dataset/prompt-set.tsx rename to app/(protected)/project/[project_id]/prompts/prompt-management.tsx index c4bccf9b..fb5a9706 100644 --- a/components/project/dataset/prompt-set.tsx +++ b/app/(protected)/project/[project_id]/prompts/prompt-management.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { CreatePromptset } from "@/components/project/dataset/create"; +import { EditPromptSet } from "@/components/project/dataset/edit"; import CardLoading from "@/components/shared/card-skeleton"; import { Card, @@ -11,10 +15,8 @@ import Link from "next/link"; import { useParams } from "next/navigation"; import { useQuery } from "react-query"; import { toast } from "sonner"; -import { CreatePromptset } from "./create"; -import { EditPromptSet } from "./edit"; -export default function PromptSet({ email }: { email: string }) { +export default function PromptManagement({ email }: { email: string }) { const projectId = useParams()?.project_id as string; const { @@ -22,18 +24,18 @@ export default function PromptSet({ email }: { email: string }) { isLoading: promptsetsLoading, error: promptsetsError, } = useQuery({ - queryKey: [`fetch-promptsets-stats-${projectId}-query`], + queryKey: ["fetch-promptsets-query", projectId], queryFn: async () => { - const response = await fetch(`/api/stats/promptset?id=${projectId}`); + const response = await fetch(`/api/promptset?id=${projectId}`); if (!response.ok) { const error = await response.json(); - throw new Error(error?.message || "Failed to fetch promptsets"); + throw new Error(error?.message || "Failed to fetch prompt sets"); } const result = await response.json(); return result; }, onError: (error) => { - toast.error("Failed to fetch promptsets", { + toast.error("Failed to fetch prompt sets", { description: error instanceof Error ? error.message : String(error), }); }, @@ -43,47 +45,46 @@ export default function PromptSet({ email }: { email: string }) { return ; } else if (promptsetsError) { return ( -

+

Failed to fetch promptsets

); } else { return ( -
+
- {promptsets?.result?.length === 0 && ( + {promptsets?.promptsets?.length === 0 && (

- Get started by creating your first prompt set. + Get started by creating your first prompt registry.

- Prompt Sets help you categorize and manage a set of prompts. Say - you would like to group the prompts that give an accuracy of 90% - of more. You can use the eval tab to add new records to any of - the prompt sets. + A Prompt registry is a collection of versioned prompts all + related to a single prompt. You can create a prompt registry, + add a prompt and continue to update and version the prompt. You + can also access the prompt using the API and use it in your + application.

)} - {promptsets?.result?.map((promptset: any, i: number) => ( + {promptsets?.promptsets?.map((promptset: any, i: number) => (
- +
- + - {promptset?.promptset?.name} + {promptset?.name}
-

{promptset?.promptset?.description}

+

{promptset?.description}

- {promptset?.totalPrompts || 0} prompts + {promptset?._count?.Prompt || 0} versions

@@ -100,13 +101,13 @@ export default function PromptSet({ email }: { email: string }) { function PageLoading() { return ( -
+
{Array.from({ length: 3 }).map((_, index) => ( diff --git a/app/(protected)/projects/page-client.tsx b/app/(protected)/projects/page-client.tsx index 1bad0cd3..fb30451f 100644 --- a/app/(protected)/projects/page-client.tsx +++ b/app/(protected)/projects/page-client.tsx @@ -178,13 +178,7 @@ function ProjectCard({ )}
- + @@ -225,7 +219,7 @@ function ProjectCard({

-

Prompt sets

+

Prompts

{projectStats?.totalPromptsets || 0}

diff --git a/app/(protected)/settings/keys/page-client.tsx b/app/(protected)/settings/keys/page-client.tsx new file mode 100644 index 00000000..aaa4f748 --- /dev/null +++ b/app/(protected)/settings/keys/page-client.tsx @@ -0,0 +1,89 @@ +"use client"; + +import AddApiKey from "@/components/shared/add-api-key"; +import CodeBlock from "@/components/shared/code-block"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { LLM_VENDORS, LLM_VENDOR_APIS } from "@/lib/constants"; +import { Trash2Icon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; + +export default function ApiKeys() { + const [busy, setBusy] = useState(false); + const [vendorKeys, setVendorKeys] = useState([]); + + useEffect(() => { + if (typeof window === "undefined") return; + const keys = LLM_VENDORS.map((vendor) => { + const keyName = LLM_VENDOR_APIS.find( + (api) => api.label.toUpperCase() === vendor.value.toUpperCase() + ); + if (!keyName) return null; + const key = window.localStorage.getItem(keyName.value.toUpperCase()); + if (!key) return null; + return { value: keyName.value.toUpperCase(), label: vendor.label, key }; + }); + if (keys.length === 0) return setVendorKeys([]); + // filter out null values + setVendorKeys(keys.filter(Boolean)); + }, [busy]); + + return ( +
+
+ setBusy(!busy)} /> +
+ + + API Keys + Add/Manage your API Keys +

+ {" "} + Note: We do not store your API keys and we use the browser store to + save it ONLY for the session. Clearing the browser cache will remove + the keys. +

+
+ +
+ {vendorKeys.length === 0 && ( +

+ There are no API keys stored +

+ )} + {vendorKeys.map((vendor) => { + return ( +
+ +
+ + +
+
+ ); + })} +
+
+
+
+ ); +} diff --git a/app/(protected)/settings/keys/page.tsx b/app/(protected)/settings/keys/page.tsx new file mode 100644 index 00000000..b5fc35a9 --- /dev/null +++ b/app/(protected)/settings/keys/page.tsx @@ -0,0 +1,23 @@ +import { authOptions } from "@/lib/auth/options"; +import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; +import PageClient from "./page-client"; + +export default async function Page() { + const session = await getServerSession(authOptions); + if (!session || !session.user) { + redirect("/login"); + } + const email = session?.user?.email as string; + + const resp = await fetch( + `${process.env.NEXTAUTH_URL_INTERNAL}/api/user?email=${email}` + ); + const user = await resp.json(); + + return ( + <> + + + ); +} diff --git a/app/api/chat/anthropic/route.ts b/app/api/chat/anthropic/route.ts new file mode 100644 index 00000000..e3625519 --- /dev/null +++ b/app/api/chat/anthropic/route.ts @@ -0,0 +1,37 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { AnthropicStream, StreamingTextResponse } from "ai"; +import { NextResponse } from "next/server"; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const isStream = data.stream; + const apiKey = data.apiKey; + + // Create an Anthropic API client + const anthropic = new Anthropic({ + apiKey: apiKey, + }); + + // remove apiKey from the body + delete data.apiKey; + + // Ask OpenAI for a streaming chat completion given the prompt + const response = await anthropic.messages.create({ + ...data, + }); + + // Convert the response into a friendly text-stream + if (isStream) { + const stream = AnthropicStream(response as any); + return new StreamingTextResponse(stream); + } + + return NextResponse.json(response); + } catch (error: any) { + return NextResponse.json({ + error: error?.message || "Something went wrong", + status: error?.status || error?.message.includes("apiKey") ? 401 : 500, + }); + } +} diff --git a/app/api/chat/cohere/route.ts b/app/api/chat/cohere/route.ts new file mode 100644 index 00000000..33f0b806 --- /dev/null +++ b/app/api/chat/cohere/route.ts @@ -0,0 +1,40 @@ +import { CohereStream, StreamingTextResponse } from "ai"; +import { CohereClient } from "cohere-ai"; +import { NextResponse } from "next/server"; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const isStream = data.stream; + const apiKey = data.apiKey; + + // Create an Cohere API client + const cohere = new CohereClient({ + token: apiKey, + }); + + // remove apiKey from the body + delete data.apiKey; + + // Ask cohere for a streaming chat completion given the prompt + const response = await cohere.chat({ + ...data, + }); + + // Convert the response into a friendly text-stream + if (isStream) { + const stream = CohereStream(response as any); + return new StreamingTextResponse(stream); + } + + return NextResponse.json(response); + } catch (error: any) { + return NextResponse.json({ + error: error?.message || "Something went wrong", + status: + error?.status || error?.message.includes("Status code: 401") + ? 401 + : 500, + }); + } +} diff --git a/app/api/chat/groq/route.ts b/app/api/chat/groq/route.ts new file mode 100644 index 00000000..d175dd62 --- /dev/null +++ b/app/api/chat/groq/route.ts @@ -0,0 +1,37 @@ +import { OpenAIStream, StreamingTextResponse } from "ai"; +import Groq from "groq-sdk"; +import { NextResponse } from "next/server"; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const isStream = data.stream; + const apiKey = data.apiKey; + + // Create an Groq API client (that's edge friendly!) + const groq = new Groq({ + apiKey: apiKey, + }); + + // remove apiKey from the body + delete data.apiKey; + + // Ask Groq for a streaming chat completion given the prompt + const response = await groq.chat.completions.create({ + ...data, + }); + + // Convert the response into a friendly text-stream + if (isStream) { + const stream = OpenAIStream(response as any); + return new StreamingTextResponse(stream); + } + + return NextResponse.json(response); + } catch (error: any) { + return NextResponse.json({ + error: error?.message || "Something went wrong", + status: error?.status || 500, + }); + } +} diff --git a/app/api/chat/openai/route.ts b/app/api/chat/openai/route.ts new file mode 100644 index 00000000..8eeb3293 --- /dev/null +++ b/app/api/chat/openai/route.ts @@ -0,0 +1,37 @@ +import { OpenAIStream, StreamingTextResponse } from "ai"; +import { NextResponse } from "next/server"; +import OpenAI from "openai"; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const isStream = data.stream; + const apiKey = data.apiKey; + + // Create an OpenAI API client (that's edge friendly!) + const openai = new OpenAI({ + apiKey: apiKey, + }); + + // remove apiKey from the body + delete data.apiKey; + + // Ask OpenAI for a streaming chat completion given the prompt + const response = await openai.chat.completions.create({ + ...data, + }); + + // Convert the response into a friendly text-stream + if (isStream) { + const stream = OpenAIStream(response as any); + return new StreamingTextResponse(stream); + } + + return NextResponse.json(response); + } catch (error: any) { + return NextResponse.json({ + error: error?.message || "Something went wrong", + status: error?.status || 500, + }); + } +} diff --git a/app/api/prompt/route.ts b/app/api/prompt/route.ts index 3160b72d..8bd8ccd0 100644 --- a/app/api/prompt/route.ts +++ b/app/api/prompt/route.ts @@ -1,49 +1,159 @@ import { authOptions } from "@/lib/auth/options"; import prisma from "@/lib/prisma"; -import { TraceService } from "@/lib/services/trace_service"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { NextRequest, NextResponse } from "next/server"; export async function GET(req: NextRequest) { const session = await getServerSession(authOptions); - if (!session || !session.user) { redirect("/login"); } - try { - const projectId = req.nextUrl.searchParams.get("projectId") as string; - const page = - (req.nextUrl.searchParams.get("page") as unknown as number) || 1; - const pageSize = - (req.nextUrl.searchParams.get("pageSize") as unknown as number) || 10; - - if (!projectId) { - return NextResponse.json( - { message: "Please provide a projectId or spanId" }, - { status: 400 } - ); - } - const traceService = new TraceService(); - const prompts = await traceService.GetSpansWithAttribute( - "llm.prompts", - projectId, - page, - pageSize - ); + const id = req.nextUrl.searchParams.get("id") as string; + if (!id) { return NextResponse.json( - { prompts }, { - status: 200, - } + error: "No prompt id provided", + }, + { status: 404 } ); - } catch (error) { - return NextResponse.json(JSON.stringify({ message: error }), { - status: 500, + } + + const result = await prisma.prompt.findFirst({ + where: { + id, + }, + }); + + return NextResponse.json({ + data: result, + }); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || !session.user) { + redirect("/login"); + } + + const data = await req.json(); + const { + value, + variables, + model, + modelSettings, + version, + live, + note, + promptsetId, + } = data; + const dataToAdd: any = { + value, + variables, + model, + modelSettings, + version, + live, + note, + promptsetId, + }; + + if (data.spanId) { + dataToAdd.spanId = data.spanId; + } + + if (live) { + const existingLivePrompt = await prisma.prompt.findFirst({ + where: { + live: true, + }, + }); + + if (existingLivePrompt) { + await prisma.prompt.update({ + where: { + id: existingLivePrompt.id, + }, + data: { + live: false, + }, + }); + } + } + + const result = await prisma.prompt.create({ + data: dataToAdd, + }); + + return NextResponse.json({ + data: result, + }); +} + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || !session.user) { + redirect("/login"); + } + + const data = await req.json(); + const { + id, + value, + variables, + model, + modelSettings, + version, + live, + note, + promptsetId, + } = data; + const dataToUpdate: any = { + value, + variables, + model, + modelSettings, + version, + live, + note, + promptsetId, + }; + + if (data.spanId) { + dataToUpdate.spanId = data.spanId; + } + + if (live) { + const existingLivePrompt = await prisma.prompt.findFirst({ + where: { + live: true, + }, }); + + if (existingLivePrompt) { + await prisma.prompt.update({ + where: { + id: existingLivePrompt.id, + }, + data: { + live: false, + }, + }); + } } + + const result = await prisma.prompt.update({ + where: { + id, + }, + data: dataToUpdate, + }); + + return NextResponse.json({ + data: result, + }); } export async function DELETE(req: NextRequest) { diff --git a/app/api/promptdata/route.ts b/app/api/promptdata/route.ts deleted file mode 100644 index ff41133d..00000000 --- a/app/api/promptdata/route.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { authOptions } from "@/lib/auth/options"; -import prisma from "@/lib/prisma"; -import { getServerSession } from "next-auth"; -import { redirect } from "next/navigation"; -import { NextRequest, NextResponse } from "next/server"; - -export async function GET(req: NextRequest) { - try { - const session = await getServerSession(authOptions); - if (!session || !session.user) { - redirect("/login"); - } - - const id = req.nextUrl.searchParams.get("id") as string; - const spanId = req.nextUrl.searchParams.get("spanId") as string; - if (!spanId && !id) { - return NextResponse.json( - { - message: "No span id or prompt id provided", - }, - { status: 404 } - ); - } - - if (id) { - const result = await prisma.prompt.findFirst({ - where: { - id, - }, - }); - - return NextResponse.json({ - data: result, - }); - } - - if (spanId) { - const result = await prisma.prompt.findMany({ - where: { - spanId, - }, - }); - - return NextResponse.json({ - data: result, - }); - } - } catch (error) { - return NextResponse.json( - { - message: "Internal server error", - }, - { status: 500 } - ); - } -} - -export async function POST(req: NextRequest) { - const session = await getServerSession(authOptions); - if (!session || !session.user) { - redirect("/login"); - } - - const data = await req.json(); - const { datas, promptsetId } = data; - - const result = await prisma.prompt.createMany({ - data: datas.map((data: any) => { - return { - value: data.value, - note: data.note || "", - spanId: data.spanId || "", - promptsetId, - }; - }), - }); - - return NextResponse.json({ - data: result, - }); -} - -export async function PUT(req: NextRequest) { - const session = await getServerSession(authOptions); - if (!session || !session.user) { - redirect("/login"); - } - - const data = await req.json(); - const { id, value, note } = data; - - const result = await prisma.prompt.update({ - where: { - id, - }, - data: { - value, - note, - }, - }); - - return NextResponse.json({ - data: result, - }); -} - -export async function DELETE(req: NextRequest) { - const session = await getServerSession(authOptions); - if (!session || !session.user) { - redirect("/login"); - } - - const data = await req.json(); - const { id } = data; - - const result = await prisma.prompt.delete({ - where: { - id, - }, - }); - - return NextResponse.json({}); -} diff --git a/app/api/promptset/route.ts b/app/api/promptset/route.ts index e9ff2060..404d806f 100644 --- a/app/api/promptset/route.ts +++ b/app/api/promptset/route.ts @@ -33,9 +33,6 @@ export async function GET(req: NextRequest) { where: { id: promptsetId, }, - include: { - Prompt: true, - }, }); if (!promptset) { @@ -77,7 +74,7 @@ export async function GET(req: NextRequest) { // Combine dataset with its related, ordered Data const promptsetWithOrderedData = { ...promptset, - Prompt: relatedPrompt, + prompts: relatedPrompt, }; return NextResponse.json({ @@ -106,12 +103,16 @@ export async function GET(req: NextRequest) { where: { projectId: id, }, - include: { - Prompt: true, - }, orderBy: { createdAt: "desc", }, + include: { + _count: { + select: { + Prompt: true, + }, + }, + }, }); return NextResponse.json({ diff --git a/app/api/span-prompt/route.ts b/app/api/span-prompt/route.ts new file mode 100644 index 00000000..3160b72d --- /dev/null +++ b/app/api/span-prompt/route.ts @@ -0,0 +1,66 @@ +import { authOptions } from "@/lib/auth/options"; +import prisma from "@/lib/prisma"; +import { TraceService } from "@/lib/services/trace_service"; +import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(req: NextRequest) { + const session = await getServerSession(authOptions); + + if (!session || !session.user) { + redirect("/login"); + } + try { + const projectId = req.nextUrl.searchParams.get("projectId") as string; + const page = + (req.nextUrl.searchParams.get("page") as unknown as number) || 1; + const pageSize = + (req.nextUrl.searchParams.get("pageSize") as unknown as number) || 10; + + if (!projectId) { + return NextResponse.json( + { message: "Please provide a projectId or spanId" }, + { status: 400 } + ); + } + + const traceService = new TraceService(); + const prompts = await traceService.GetSpansWithAttribute( + "llm.prompts", + projectId, + page, + pageSize + ); + + return NextResponse.json( + { prompts }, + { + status: 200, + } + ); + } catch (error) { + return NextResponse.json(JSON.stringify({ message: error }), { + status: 500, + }); + } +} + +export async function DELETE(req: NextRequest) { + const session = await getServerSession(authOptions); + + if (!session || !session.user) { + redirect("/login"); + } + + const data = await req.json(); + const { id } = data; + + const prompt = await prisma.prompt.delete({ + where: { + id, + }, + }); + + return NextResponse.json({}); +} diff --git a/components/evaluations/evaluation-row.tsx b/components/evaluations/evaluation-row.tsx index f303f5cc..750131eb 100644 --- a/components/evaluations/evaluation-row.tsx +++ b/components/evaluations/evaluation-row.tsx @@ -1,5 +1,3 @@ -"use client"; - import { HoverCell } from "@/components/shared/hover-cell"; import { LLMView } from "@/components/shared/llm-view"; import { Button } from "@/components/ui/button"; diff --git a/components/evaluations/evaluation-table.tsx b/components/evaluations/evaluation-table.tsx index 2f4d536e..b48b367b 100644 --- a/components/evaluations/evaluation-table.tsx +++ b/components/evaluations/evaluation-table.tsx @@ -1,5 +1,3 @@ -"use client"; - import { TestSetupInstructions } from "@/components/shared/setup-instructions"; import { Spinner } from "@/components/shared/spinner"; import { PAGE_SIZE } from "@/lib/constants"; diff --git a/components/playground/chat-handlers.ts b/components/playground/chat-handlers.ts new file mode 100644 index 00000000..6c498064 --- /dev/null +++ b/components/playground/chat-handlers.ts @@ -0,0 +1,301 @@ +import { + AnthropicChatInterface, + CohereChatInterface, + GroqChatInterface, + OpenAIChatInterface, +} from "@/lib/types/playground_types"; + +export async function openAIHandler( + llm: OpenAIChatInterface, + apiKey: string +): Promise { + const body: any = {}; + if (llm.settings.messages.length > 0) { + body.messages = llm.settings.messages.map((m) => { + return { content: m.content, role: m.role }; + }); + } + if (llm.settings.model) { + body.model = llm.settings.model; + } + if (llm.settings.temperature) { + body.temperature = llm.settings.temperature; + } + if (llm.settings.maxTokens) { + body.max_tokens = llm.settings.maxTokens; + } + if (llm.settings.n) { + body.n = llm.settings.n; + } + if (llm.settings.stop) { + body.stop = llm.settings.stop; + } + if (llm.settings.frequencyPenalty) { + body.frequency_penalty = llm.settings.frequencyPenalty; + } + if (llm.settings.presencePenalty) { + body.presence_penalty = llm.settings.presencePenalty; + } + if (llm.settings.logProbs) { + body.logprobs = llm.settings.logProbs; + } + if (llm.settings.topLogProbs) { + body.top_logprobs = llm.settings.topLogProbs; + } + if (llm.settings.logitBias !== undefined) { + body.logit_bias = llm.settings.logitBias; + } + if (llm.settings.responseFormat) { + body.response_format = llm.settings.responseFormat; + } + if (llm.settings.seed) { + body.seed = llm.settings.seed; + } + if (llm.settings.stream !== undefined) { + body.stream = llm.settings.stream; + } + if (llm.settings.topP) { + body.top_p = llm.settings.topP; + } + if (llm.settings.tools && llm.settings.tools.length > 0) { + body.tools = llm.settings.tools; + } + if (llm.settings.toolChoice) { + body.tool_choice = llm.settings.toolChoice; + } + if (llm.settings.user) { + body.user = llm.settings.user; + } + + // Get the API key from the browser store + body.apiKey = apiKey; + + const response = await fetch("/api/chat/openai", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return response; +} + +export async function anthropicHandler( + llm: AnthropicChatInterface, + apiKey: string +): Promise { + const body: any = {}; + if (llm.settings.messages.length > 0) { + body.messages = llm.settings.messages.map((m) => { + return { content: m.content, role: m.role }; + }); + } + if (llm.settings.model) { + body.model = llm.settings.model; + } + if (llm.settings.temperature) { + body.temperature = llm.settings.temperature; + } + if (llm.settings.maxTokens) { + body.max_tokens = llm.settings.maxTokens; + } + if (llm.settings.stream !== undefined) { + body.stream = llm.settings.stream; + } + if (llm.settings.topP) { + body.top_p = llm.settings.topP; + } + if (llm.settings.tools && llm.settings.tools.length > 0) { + body.tools = llm.settings.tools; + } + if (llm.settings.topK) { + body.top_k = llm.settings.topK; + } + if (llm.settings.metadata) { + body.metadata = llm.settings.metadata; + } + if (llm.settings.system) { + body.system = llm.settings.system; + } + + // Get the API key from the browser store + body.apiKey = apiKey; + + const response = await fetch("/api/chat/anthropic", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return response; +} + +export async function cohereHandler( + llm: CohereChatInterface, + apiKey: string +): Promise { + const body: any = {}; + if (llm.settings.messages.length > 0) { + body.message = + llm.settings.messages[llm.settings.messages.length - 1].content; + body.chat_history = llm.settings.messages.map((m, i) => { + if (i === llm.settings.messages.length - 1) return null; + return { message: m.content, role: m.role }; + }); + } + // remove null values + body.chat_history = body.chat_history.filter(Boolean); + + if (llm.settings.model) { + body.model = llm.settings.model; + } + if (llm.settings.temperature) { + body.temperature = llm.settings.temperature; + } + if (llm.settings.maxTokens) { + body.max_tokens = llm.settings.maxTokens; + } + if (llm.settings.maxInputTokens) { + body.max_input_tokens = llm.settings.maxInputTokens; + } + if (llm.settings.stream !== undefined) { + body.stream = llm.settings.stream; + } + if (llm.settings.preamble) { + body.preamble = llm.settings.preamble; + } + if (llm.settings.conversationId) { + body.conversation_id = llm.settings.conversationId; + } + if (llm.settings.promptTruncation) { + body.prompt_truncation = llm.settings.promptTruncation; + } + if (llm.settings.connectors) { + body.connectors = llm.settings.connectors; + } + if (llm.settings.searchQueriesOnly) { + body.search_queries_only = llm.settings.searchQueriesOnly; + } + if (llm.settings.documents) { + body.documents = llm.settings.documents; + } + if (llm.settings.citationQuality) { + body.citation_quality = llm.settings.citationQuality; + } + if (llm.settings.k) { + body.k = llm.settings.k; + } + if (llm.settings.p) { + body.p = llm.settings.p; + } + if (llm.settings.seed) { + body.seed = llm.settings.seed; + } + if (llm.settings.stopSequences) { + body.stop_sequences = llm.settings.stopSequences; + } + if (llm.settings.frequencyPenalty) { + body.frequency_penalty = llm.settings.frequencyPenalty; + } + if (llm.settings.presencePenalty) { + body.presence_penalty = llm.settings.presencePenalty; + } + if (llm.settings.tools && llm.settings.tools.length > 0) { + body.tools = llm.settings.tools; + } + if (llm.settings.toolResults) { + body.tool_results = llm.settings.toolResults; + } + + // Get the API key from the browser store + body.apiKey = apiKey; + + const response = await fetch("/api/chat/cohere", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return response; +} + +export async function groqHandler( + llm: GroqChatInterface, + apiKey: string +): Promise { + const body: any = {}; + if (llm.settings.messages.length > 0) { + body.messages = llm.settings.messages.map((m) => { + return { content: m.content, role: m.role }; + }); + } + if (llm.settings.model) { + body.model = llm.settings.model; + } + if (llm.settings.temperature) { + body.temperature = llm.settings.temperature; + } + if (llm.settings.maxTokens) { + body.max_tokens = llm.settings.maxTokens; + } + if (llm.settings.n) { + body.n = llm.settings.n; + } + if (llm.settings.stop) { + body.stop = llm.settings.stop; + } + if (llm.settings.frequencyPenalty) { + body.frequency_penalty = llm.settings.frequencyPenalty; + } + if (llm.settings.presencePenalty) { + body.presence_penalty = llm.settings.presencePenalty; + } + if (llm.settings.logProbs) { + body.logprobs = llm.settings.logProbs; + } + if (llm.settings.topLogProbs) { + body.top_logprobs = llm.settings.topLogProbs; + } + if (llm.settings.logitBias !== undefined) { + body.logit_bias = llm.settings.logitBias; + } + if (llm.settings.responseFormat) { + body.response_format = llm.settings.responseFormat; + } + if (llm.settings.seed) { + body.seed = llm.settings.seed; + } + if (llm.settings.stream !== undefined) { + body.stream = llm.settings.stream; + } + if (llm.settings.topP) { + body.top_p = llm.settings.topP; + } + if (llm.settings.tools && llm.settings.tools.length > 0) { + body.tools = llm.settings.tools; + } + if (llm.settings.toolChoice) { + body.tool_choice = llm.settings.toolChoice; + } + if (llm.settings.user) { + body.user = llm.settings.user; + } + + // Get the API key from the browser store + body.apiKey = apiKey; + + const response = await fetch("/api/chat/groq", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + return response; +} diff --git a/components/playground/common.tsx b/components/playground/common.tsx new file mode 100644 index 00000000..e68fef71 --- /dev/null +++ b/components/playground/common.tsx @@ -0,0 +1,169 @@ +import LLMPicker from "@/components/shared/llm-picker"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { + CohereAIRole, + Conversation, + OpenAIRole, +} from "@/lib/types/playground_types"; +import { cn } from "@/lib/utils"; +import { MinusCircleIcon, PlusIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; + +export function RoleBadge({ + role, + onSelect, +}: { + role: OpenAIRole | CohereAIRole; + onSelect: () => void; +}) { + return ( + + ); +} + +export function ExpandingTextArea({ + value, + onChange, + setFocusing, +}: { + value: string; + onChange: any; + setFocusing?: any; +}) { + const textAreaRef = useRef(null); + + const handleClickOutside = (event: any) => { + if (textAreaRef.current && !textAreaRef.current.contains(event.target)) { + setFocusing(false); + } + }; + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + const handleChange = (event: any) => { + const textarea = event.target; + onChange(textarea.value); + + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + }; + + return ( +