From 5622f8452766a542ba8fa6e535dbc852cf7b988e Mon Sep 17 00:00:00 2001 From: "andrii.dudar" Date: Tue, 15 Oct 2024 10:39:00 +0200 Subject: [PATCH] [OPIK-220] [UX Improvement] Add quickstart page --- .../public/images/colab-logo.png | Bin 0 -> 1837 bytes .../src/components/layout/SideBar/SideBar.tsx | 126 +++++++--- .../pages/GetStartedPage/GetStarted.tsx | 146 ++++++++++++ .../pages/GetStartedPage/GetStartedPage.tsx | 8 +- .../pages/QuickstartPage/Quickstart.tsx | 221 ++++++++++++++++++ .../pages/QuickstartPage/QuickstartPage.tsx | 14 ++ .../integrations/FunctionDecorators.tsx | 30 +++ .../integrations/IntegrationTemplate.tsx | 53 +++++ .../QuickstartPage/integrations/LangChain.tsx | 43 ++++ .../QuickstartPage/integrations/LiteLLM.tsx | 33 +++ .../QuickstartPage/integrations/OpenAI.tsx | 42 ++++ .../QuickstartPage/integrations/Ragas.tsx | 42 ++++ .../QuickstartPage/integrations/types.ts | 3 + .../shared/ApiKeyInput/ApiKeyInput.tsx | 31 +++ apps/opik-frontend/src/lib/utils.ts | 2 +- .../src/plugins/comet/GetStartedPage.tsx | 148 +----------- .../src/plugins/comet/QuickstartPage.tsx | 11 + .../src/plugins/comet/UserMenu.tsx | 4 +- apps/opik-frontend/src/router.tsx | 13 +- apps/opik-frontend/src/store/PluginsStore.ts | 3 + 20 files changed, 785 insertions(+), 188 deletions(-) create mode 100644 apps/opik-frontend/public/images/colab-logo.png create mode 100644 apps/opik-frontend/src/components/pages/GetStartedPage/GetStarted.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/Quickstart.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/QuickstartPage.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/FunctionDecorators.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/IntegrationTemplate.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LangChain.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LiteLLM.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/OpenAI.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/Ragas.tsx create mode 100644 apps/opik-frontend/src/components/pages/QuickstartPage/integrations/types.ts create mode 100644 apps/opik-frontend/src/components/shared/ApiKeyInput/ApiKeyInput.tsx create mode 100644 apps/opik-frontend/src/plugins/comet/QuickstartPage.tsx diff --git a/apps/opik-frontend/public/images/colab-logo.png b/apps/opik-frontend/public/images/colab-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9174c468f6d531913ecd115bf7b84f8008ca71c3 GIT binary patch literal 1837 zcmV+|2h#Y7P)^3* zbC<}00|yQqIB?*=fddD*Lf5IFCrEch1N3}UB_*y>8Mr-KN84gST95CSC+g@a{V?6g zZ8M=OK(|F}=~?*l3BU0Fc}%4p@j9xE)zK>WeyL|aO%^e5+iMQdX>T395D!uff@uTw zbcBcoe14JGM|1U38pHh=fdP9I?LUKPjr-xmVEl_gQStWzeJ8oOuWN$DUYd>BI2j92 z9}g%CMlo}HBPvbhe)0YN5Pk@4VitCp*oOf-8L6c?-0!GC-%f?-=p)gYMF6?-at~&D zz%Nk&R?<88E^5&(SN0}U`dI&s3VHk^f*zo;*pCM-I?Rh$x@~%pCW@^D@tgVckrMuE zM9f@AUif1njSVTDJ#7_*y}YA5*^dC<629^GI*aEcW=EAP(OfJa)R&FIO3-}LhOl4O zLtsZ`VS;y-D=*oDfpn^TK|qQ#eZwLVL9< zT@Dbl8^-5li)TW7BvM0<>*DBCsfj;N!+s>mOZaRAZC7GPwJWZbNPWq82L{6Dev<3T z3Qfx<$zh*-+Td)E#d3cqmRPFe0CizDcSeFVM?Xr}C++nkILC2TBeNe(wne7iCH6i3 z4)EYjR4IytwhyPyGkQ5mDZlmP?FrCq4CW@|Nz`6h*p-}lBQ?K#8u{S5qhw6cxa1Nqoaa$uq=Lqyw1bhL3-!9I1*mau(8;G_Y5dUrnt=g6*H-Sb z6hB0KFD>S_X(2jpQlwKB&Wt>(SI`aI)(-ma!M8yKyp;lW8!Ap}{wPYFLpIW=ARZUR>ds&h0Wm|B^hI-BvDo7*(3j?J}WhxjYjhJ1pW% zGNG*;e>bs@ zm)@0E;Q^5!a?wd5nO+g~0Jq5&;&jpcp1*j!DJt?2Zet5%`JYS zp3QBt0ZQP-7Q$0Z7D@R4S^g2k7Pp9Zh*CMXv4!RvH{a~wDv=1k<~G?NzL%y$Xg?R8 zVt{@%zE0-?%y8Ocuvw87avNJ*UhSR&qbPS`U{6z#H|N_IBsp(%u%I!EA7p=|j+St{ zOlT~UX5pRRSOjs#Lq7Li+%6pouw;I)aAahwUPF_(p&j(&G+umSnO9f{H?p^VgOs+u zFyex5!bHTg>TA!aiXq@(T*T92if=gknta{`+&&}3h#`>gO%5Tx zYl|zM3R)eg)q|q+MM-=o?8b=uL;WC4i3{N>{Sv#n?+=UT_sM;pF zM5GpF?lHP)*tK_UL0{X3u`y9*n+1%3ihQN9B>U}TiLl~XBb;f9+}F9;F!T8`K);L9 z7=!dmZ0Lr$y}U4D3Vsxb0sVmM$r(CfuNBL?I=8y=xv#gR_{B*0M#f*)UREgX@Jzvv zl$zq*CbkUMlNZFT4ia9v_xRkXQb+gD2_jaQSY2Ge$msd1KF_yRTZp8HK`2HB?=%U@ zk>b`z+zljsYl`~~oa14$klctRpFr8WI^C%>V(2POgNOrQgbT|=akC5Yl^x&E1n&2e zB2Cs5&u$#Q;v_JM%fL9qAH~c+$o*auh!IZI(cGx`B{)ETbD@mANPMT!evSAgnfu~s zMb^fa*o3X|XQopD?Ji;EY}I_yW8BaGhL#;P4t4&+5c)c}3j2F4K1;>V+OZ(LjQ^+Q zQ|Id?w|ZJp;57T)OEsVTe#r0nH01M?VNlk#;3cWW?|q_kjrV45=fHsj2M!!KaNxj! bgRJl$P)N35ydRy~00000NkvXXu0mjfH28uP literal 0 HcmV?d00001 diff --git a/apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx b/apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx index 19dbc2689..ff8c013d5 100644 --- a/apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx +++ b/apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx @@ -2,9 +2,12 @@ import React from "react"; import isNumber from "lodash/isNumber"; import { Link, useMatchRoute } from "@tanstack/react-router"; import { + Book, Database, FlaskConical, + GraduationCap, LayoutGrid, + LucideIcon, MessageSquare, PanelRightOpen, } from "lucide-react"; @@ -17,45 +20,78 @@ import useExperimentsList from "@/api/datasets/useExperimentsList"; import useFeedbackDefinitionsList from "@/api/feedback-definitions/useFeedbackDefinitionsList"; import { OnChangeFn } from "@/types/shared"; import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper"; -import { cn } from "@/lib/utils"; +import { buildDocsUrl, cn } from "@/lib/utils"; import Logo from "@/components/layout/Logo/Logo"; import usePluginsStore from "@/store/PluginsStore"; -const ITEMS = [ +enum MENU_ITEM_TYPE { + link = "link", + router = "router", +} + +type MenuItem = { + path: string; + type: MENU_ITEM_TYPE; + icon: LucideIcon; + label: string; + count?: string; +}; + +const MAIN_MENU_ITEMS: MenuItem[] = [ { path: "/$workspaceName/projects", + type: MENU_ITEM_TYPE.router, icon: LayoutGrid, label: "Projects", count: "projects", }, { path: "/$workspaceName/datasets", + type: MENU_ITEM_TYPE.router, icon: Database, label: "Datasets", count: "datasets", }, { path: "/$workspaceName/experiments", + type: MENU_ITEM_TYPE.router, icon: FlaskConical, label: "Experiments", count: "experiments", }, { path: "/$workspaceName/feedback-definitions", + type: MENU_ITEM_TYPE.router, icon: MessageSquare, label: "Feedback definitions", count: "feedbackDefinitions", }, ]; +const BOTTOM_MENU_ITEMS = [ + { + path: buildDocsUrl(), + type: MENU_ITEM_TYPE.link, + icon: Book, + label: "Documentation", + }, + { + path: "/$workspaceName/quickstart", + type: MENU_ITEM_TYPE.router, + icon: GraduationCap, + label: "Quickstart guide", + }, +]; + +const HOME_PATH = "/$workspaceName/projects"; + type SideBarProps = { expanded: boolean; setExpanded: OnChangeFn; }; -const HOME_PATH = "/$workspaceName/projects"; - const SideBar: React.FunctionComponent = ({ expanded, setExpanded, @@ -141,35 +177,55 @@ const SideBar: React.FunctionComponent = ({ ); - const renderItems = () => { - return ITEMS.map((item) => { + const renderItems = (items: MenuItem[]) => { + return items.map((item) => { const hasCount = item.count && isNumber(countDataMap[item.count]); - const count = hasCount ? countDataMap[item.count] : ""; - - const itemElement = ( -
  • - - - {expanded && ( - <> -
    {item.label}
    - {hasCount && ( -
    {count}
    - )} - - )} - -
  • + const count = hasCount ? countDataMap[item.count!] : ""; + + const content = ( + <> + + {expanded && ( + <> +
    {item.label}
    + {hasCount && ( +
    {count}
    + )} + + )} + + ); + + const linkClasses = cn( + "comet-body-s flex h-9 w-full items-center gap-2 text-foreground rounded-md hover:bg-primary-foreground data-[status=active]:bg-primary-100 data-[status=active]:text-primary", + expanded ? "pl-[10px] pr-3" : "w-9 justify-center", ); + const itemElement = + item.type === MENU_ITEM_TYPE.router ? ( +
  • + + {content} + +
  • + ) : ( +
  • + + {content} + +
  • + ); + if (expanded) { return itemElement; } @@ -203,8 +259,14 @@ const SideBar: React.FunctionComponent = ({ )} -
    -
      {renderItems()}
    +
    +
      {renderItems(MAIN_MENU_ITEMS)}
    +
    + +
      + {renderItems(BOTTOM_MENU_ITEMS)} +
    +
    ); diff --git a/apps/opik-frontend/src/components/pages/GetStartedPage/GetStarted.tsx b/apps/opik-frontend/src/components/pages/GetStartedPage/GetStarted.tsx new file mode 100644 index 000000000..4ef56dd1b --- /dev/null +++ b/apps/opik-frontend/src/components/pages/GetStartedPage/GetStarted.tsx @@ -0,0 +1,146 @@ +import { MoveRight, SquareArrowOutUpRight } from "lucide-react"; +import { Link } from "@tanstack/react-router"; +import { Button } from "@/components/ui/button"; +import useAppStore from "@/store/AppStore"; +import demoProjectImageUrl from "/images/demo-project.png"; +import langChainLogoUrl from "/images/integrations/langchain.png"; +import liteLLMLogoUrl from "/images/integrations/litellm.png"; +import openAILogoUrl from "/images/integrations/openai.png"; +import pythonLogoUrl from "/images/integrations/python.png"; +import ragasLogoUrl from "/images/integrations/ragas.png"; +import ApiKeyInput from "@/components/shared/ApiKeyInput/ApiKeyInput"; +import React from "react"; + +const LOGO_IMAGES = [ + pythonLogoUrl, + liteLLMLogoUrl, + openAILogoUrl, + ragasLogoUrl, + langChainLogoUrl, +]; + +type GetStartedProps = { + apiKey?: string; + userName: string; +}; + +const GetStarted: React.FunctionComponent = ({ + apiKey, + userName, +}) => { + const workspaceName = useAppStore((state) => state.activeWorkspaceName); + + return ( +
    +
    +
    +
    +
    +
    + Welcome {userName} +
    + +

    + Get Started with Opik +

    +
    + + + + +
    + +
    + Start with one of our integrations or a few lines of code to log, + view and evaluate your LLM traces during both development and + production. +
    +
    + +
    +
    +
    +
    +
    + Integrate Opik +
    +
    + A step by step guide for adding Opik into your existing + application. Includes detailed examples. +
    +
    + + + +
    +
    + +
    + {LOGO_IMAGES.map((url) => { + return ; + })} +
    +
    + + {apiKey && ( +
    +
    +
    + Copy your API key +
    +
    + API keys are used to send traces to the Opik platform. +
    +
    + + +
    + )} +
    + +
    +
    Explore demo project
    + + + +
    +
    +
    + SQL query generation +
    +
    + Perform a text to SQL query generation using LangChain. The + example uses the Chinook database of a music store, with + both employee, customer and invoice data. +
    +
    +
    + Explore project +
    +
    + +
    +
    +
    +
    + ); +}; + +export default GetStarted; diff --git a/apps/opik-frontend/src/components/pages/GetStartedPage/GetStartedPage.tsx b/apps/opik-frontend/src/components/pages/GetStartedPage/GetStartedPage.tsx index cb62d9918..39e0d3480 100644 --- a/apps/opik-frontend/src/components/pages/GetStartedPage/GetStartedPage.tsx +++ b/apps/opik-frontend/src/components/pages/GetStartedPage/GetStartedPage.tsx @@ -1,18 +1,14 @@ -import useAppStore from "@/store/AppStore"; import usePluginsStore from "@/store/PluginsStore"; -import { Navigate } from "@tanstack/react-router"; +import GetStarted from "@/components/pages/GetStartedPage/GetStarted"; const GetStartedPage = () => { - const workspaceName = useAppStore((state) => state.activeWorkspaceName); const GetStartedPage = usePluginsStore((state) => state.GetStartedPage); if (GetStartedPage) { return ; } - return ( - - ); + return ; }; export default GetStartedPage; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/Quickstart.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/Quickstart.tsx new file mode 100644 index 000000000..e54a9dfc5 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/Quickstart.tsx @@ -0,0 +1,221 @@ +import React, { useMemo, useState } from "react"; +import { Link, useRouter, useSearch } from "@tanstack/react-router"; +import { MoveLeft, SquareArrowOutUpRight } from "lucide-react"; + +import useAppStore from "@/store/AppStore"; +import { Button } from "@/components/ui/button"; +import ApiKeyInput from "@/components/shared/ApiKeyInput/ApiKeyInput"; +import pythonLogoUrl from "/images/integrations/python.png"; +import langChainLogoUrl from "/images/integrations/langchain.png"; +import liteLLMLogoUrl from "/images/integrations/litellm.png"; +import openAILogoUrl from "/images/integrations/openai.png"; +import ragasLogoUrl from "/images/integrations/ragas.png"; +import colabLogo from "/images/colab-logo.png"; +import { buildDocsUrl } from "@/lib/utils"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import FunctionDecorators from "@/components/pages/QuickstartPage/integrations/FunctionDecorators"; +import LangChain from "@/components/pages/QuickstartPage/integrations/LangChain"; +import LiteLLM from "@/components/pages/QuickstartPage/integrations/LiteLLM"; +import OpenAI from "@/components/pages/QuickstartPage/integrations/OpenAI"; +import Ragas from "@/components/pages/QuickstartPage/integrations/Ragas"; + +type Integration = { + label: string; + logo: string; + colab: string; + documentation: string; + component: React.FC; +}; + +const INTEGRATIONS: Integration[] = [ + { + label: "Function decorators", + logo: pythonLogoUrl, + colab: + "https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/quickstart_notebook.ipynb", + documentation: buildDocsUrl( + "/tracing/log_traces", + "#using-function-decorators", + ), + component: FunctionDecorators, + }, + { + label: "LangChain", + logo: langChainLogoUrl, + colab: + "https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/langchain.ipynb", + documentation: buildDocsUrl("/tracing/integrations/langchain"), + component: LangChain, + }, + { + label: "LiteLLM", + logo: liteLLMLogoUrl, + colab: + "https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/litellm.ipynb", + documentation: buildDocsUrl("/tracing/integrations/litellm"), + component: LiteLLM, + }, + { + label: "OpenAI", + logo: openAILogoUrl, + colab: + "https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/openai.ipynb", + documentation: buildDocsUrl("/tracing/integrations/openai"), + component: OpenAI, + }, + { + label: "Ragas", + logo: ragasLogoUrl, + colab: + "https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/ragas.ipynb", + documentation: buildDocsUrl("/tracing/integrations/ragas"), + component: Ragas, + }, +]; + +type QuickstartProps = { + apiKey?: string; +}; + +const Quickstart: React.FunctionComponent = ({ apiKey }) => { + const workspaceName = useAppStore((state) => state.activeWorkspaceName); + const [integrationIndex, setIntegrationIndex] = useState(0); + const integration = useMemo( + () => INTEGRATIONS[integrationIndex], + [integrationIndex], + ); + + const router = useRouter(); + const { from }: { from?: string } = useSearch({ strict: false }); + const getBackText = + from === "get-started" ? "Return to ‘Get started’" : "Return back"; + + const renderMenuItems = () => { + return INTEGRATIONS.map((item, index) => { + return ( +
  • setIntegrationIndex(index)} + data-status={index === integrationIndex ? "active" : "inactive"} + > + {item.label} +
    {item.label}
    +
  • + ); + }); + }; + + return ( +
    +
    + +
    +
    +
    +
      {renderMenuItems()}
    + +
    +
    +
    + + + +
    +
    +
    +
    +

    Quickstart guide

    +
    + Select the framework and follow the instructions to integrate + Opik with your own code or use our ready-to-run examples on + the right. +
    + +
    +
    + +
    +
    +
    + Try one of our full examples +
    +
    +
    + Or try this end to end example in Google Colab: +
    + +
    + +
    + {apiKey && ( +
    +
    + Copy your API key +
    + +
    + )} +
    +
    +
    +
    +
    + ); +}; + +export default Quickstart; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/QuickstartPage.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/QuickstartPage.tsx new file mode 100644 index 000000000..8eb87492d --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/QuickstartPage.tsx @@ -0,0 +1,14 @@ +import usePluginsStore from "@/store/PluginsStore"; +import Quickstart from "@/components/pages/QuickstartPage/Quickstart"; + +const QuickstartPage = () => { + const QuickstartPage = usePluginsStore((state) => state.QuickstartPage); + + if (QuickstartPage) { + return ; + } + + return ; +}; + +export default QuickstartPage; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/FunctionDecorators.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/FunctionDecorators.tsx new file mode 100644 index 000000000..d1d9a4d9c --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/FunctionDecorators.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import pythonLogoUrl from "/images/integrations/python.png"; +import IntegrationTemplate from "@/components/pages/QuickstartPage/integrations/IntegrationTemplate"; + +const CODE_TITLE = + "Each function decorated by the `track` decorator will be logged to the Opik platform:"; + +const CODE = `import opik + +@opik.track +def my_llm_chain(input_text): + # Call the different parts of my chain + return “Hello, world!”`; + +const FunctionDecorators: React.FC = ({ + apiKey, +}) => { + return ( + + ); +}; + +export default FunctionDecorators; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/IntegrationTemplate.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/IntegrationTemplate.tsx new file mode 100644 index 000000000..187826261 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/IntegrationTemplate.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import CodeHighlighter from "@/components/shared/CodeHighlighter/CodeHighlighter"; + +const CODE_BLOCK_1 = "pip install opik"; + +const CODE_BLOCK_2 = `import opik +opik.configure()`; + +const CODE_BLOCK_2_LOCAL = `import opik +opik.configure(use_local=True)`; + +type IntegrationTemplateProps = IntegrationComponentProps & { + integration: string; + url: string; + codeTitle: string; + code: string; +}; + +const IntegrationTemplate: React.FC = ({ + apiKey, + integration, + url, + codeTitle, + code, +}) => { + return ( +
    +
    + {integration} +

    Integrate Opik with your own code

    +
    +
    +
    +
    + Install Opik using pip from the command line. +
    + +
    +
    +
    Configure the Opik SDK
    + +
    +
    +
    {codeTitle}
    + +
    +
    +
    + ); +}; + +export default IntegrationTemplate; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LangChain.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LangChain.tsx new file mode 100644 index 000000000..85682f3e1 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LangChain.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import langChainLogoUrl from "/images/integrations/langchain.png"; +import IntegrationTemplate from "@/components/pages/QuickstartPage/integrations/IntegrationTemplate"; + +const CODE_TITLE = + "You can use the `OpikTracer` provided by the Opik SDK to log all LangChains calls to Opik:"; + +const CODE = `from langchain.chains import LLMChain +from langchain_openai import OpenAI +from langchain.prompts import PromptTemplate +from opik.integrations.langchain import OpikTracer + +# Initialize the tracer +opik_tracer = OpikTracer() + +# Create the LLM Chain using LangChain +llm = OpenAI(temperature=0) + +prompt_template = PromptTemplate( + input_variables=["input"], + template="Translate the following text to French: {input}" +) + +llm_chain = LLMChain(llm=llm, prompt=prompt_template) + +# Generate the translations +translation = llm_chain.run("Hello, how are you?", callbacks=[opik_tracer]) +print(translation)`; + +const LangChain: React.FC = ({ apiKey }) => { + return ( + + ); +}; + +export default LangChain; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LiteLLM.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LiteLLM.tsx new file mode 100644 index 000000000..25d27d106 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/LiteLLM.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import liteLLMLogoUrl from "/images/integrations/litellm.png"; +import IntegrationTemplate from "@/components/pages/QuickstartPage/integrations/IntegrationTemplate"; + +const CODE_TITLE = "You can configure LiteLLM to log all LLM calls to Opik:"; + +const CODE = `from litellm.integrations.opik.opik import OpikLogger +import litellm + +opik_logger = OpikLogger() +litellm.callbacks = [opik_logger] + +response = litellm.completion( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Why is tracking and evaluation of LLMs important?"} + ] +)`; + +const LiteLLM: React.FC = ({ apiKey }) => { + return ( + + ); +}; + +export default LiteLLM; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/OpenAI.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/OpenAI.tsx new file mode 100644 index 000000000..4f73f6fe8 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/OpenAI.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import openAILogoUrl from "/images/integrations/openai.png"; +import IntegrationTemplate from "@/components/pages/QuickstartPage/integrations/IntegrationTemplate"; + +const CODE_TITLE = + "You can use the `track_openai` wrapper to log all OpenAI calls to the Opik platform"; + +const CODE = `from opik.integrations.openai import track_openai +from openai import OpenAI + +os.environ["OPIK_PROJECT_NAME"] = "openai-integration-demo" +client = OpenAI() + +openai_client = track_openai(client) + +prompt = """ +Write a short two sentence story about Opik. +""" + +completion = openai_client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": prompt} + ] +) + +print(completion.choices[0].message.content)`; + +const OpenAI: React.FC = ({ apiKey }) => { + return ( + + ); +}; + +export default OpenAI; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/Ragas.tsx b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/Ragas.tsx new file mode 100644 index 000000000..ae33eaf48 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/Ragas.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { IntegrationComponentProps } from "@/components/pages/QuickstartPage/integrations/types"; +import ragasLogoUrl from "/images/integrations/ragas.png"; +import IntegrationTemplate from "@/components/pages/QuickstartPage/integrations/IntegrationTemplate"; + +const CODE_TITLE = + "You can use the `OpikTracer` provided as part of the Ragas integration to log all Ragas scores to Opik:"; + +const CODE = `import asyncio + +# Import the metric +from ragas.metrics import AnswerRelevancy + +# Import some additional dependencies +from langchain_openai.chat_models import ChatOpenAI +from langchain_openai.embeddings import OpenAIEmbeddings +from ragas.embeddings import LangchainEmbeddingsWrapper +from ragas.integrations.opik import OpikTracer +from ragas.llms import LangchainLLMWrapper + +# Initialize the Ragas metric +llm = LangchainLLMWrapper(ChatOpenAI()) +emb = LangchainEmbeddingsWrapper(OpenAIEmbeddings()) +answer_relevancy_metric = AnswerRelevancy(llm=llm, embeddings=emb) + +# Call Ragas metric logging with the Opik Tracer +answer_relevancy_metric.single_turn_ascore(row, callbacks=[OpikTracer()]) +`; + +const Ragas: React.FC = ({ apiKey }) => { + return ( + + ); +}; + +export default Ragas; diff --git a/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/types.ts b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/types.ts new file mode 100644 index 000000000..581c106a9 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/QuickstartPage/integrations/types.ts @@ -0,0 +1,3 @@ +export type IntegrationComponentProps = { + apiKey?: string; +}; diff --git a/apps/opik-frontend/src/components/shared/ApiKeyInput/ApiKeyInput.tsx b/apps/opik-frontend/src/components/shared/ApiKeyInput/ApiKeyInput.tsx new file mode 100644 index 000000000..b68114093 --- /dev/null +++ b/apps/opik-frontend/src/components/shared/ApiKeyInput/ApiKeyInput.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { KeyRound } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import CopyButton from "@/components/shared/CopyButton/CopyButton"; + +type ApiKeyInputProps = { + apiKey: string; +}; + +const ApiKeyInput: React.FunctionComponent = ({ apiKey }) => { + return ( +
    + + { + e.target.blur(); + }} + /> + +
    + ); +}; + +export default ApiKeyInput; diff --git a/apps/opik-frontend/src/lib/utils.ts b/apps/opik-frontend/src/lib/utils.ts index 3afa0aaa6..e9d817450 100644 --- a/apps/opik-frontend/src/lib/utils.ts +++ b/apps/opik-frontend/src/lib/utils.ts @@ -6,7 +6,7 @@ import { twMerge } from "tailwind-merge"; const BASE_COMET_URL = import.meta.env.VITE_BASE_COMET_URL; -export const buildDocsUrl = (path: string, hash: string = "") => { +export const buildDocsUrl = (path: string = "", hash: string = "") => { const url = BASE_COMET_URL ? `${BASE_COMET_URL}docs/opik` : "https://comet.com/docs/opik"; diff --git a/apps/opik-frontend/src/plugins/comet/GetStartedPage.tsx b/apps/opik-frontend/src/plugins/comet/GetStartedPage.tsx index 5e6a94ab7..1da9767f1 100644 --- a/apps/opik-frontend/src/plugins/comet/GetStartedPage.tsx +++ b/apps/opik-frontend/src/plugins/comet/GetStartedPage.tsx @@ -1,155 +1,11 @@ -import CopyButton from "@/components/shared/CopyButton/CopyButton"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import useAppStore from "@/store/AppStore"; -import { Link } from "@tanstack/react-router"; -import { KeyRound, MoveRight, SquareArrowOutUpRight } from "lucide-react"; import useUser from "./useUser"; -import { buildUrl } from "./utils"; -import demoProjectImageUrl from "/images/demo-project.png"; -import langChainLogoUrl from "/images/integrations/langchain.png"; -import liteLLMLogoUrl from "/images/integrations/litellm.png"; -import openAILogoUrl from "/images/integrations/openai.png"; -import pythonLogoUrl from "/images/integrations/python.png"; -import ragasLogoUrl from "/images/integrations/ragas.png"; - -const LOGO_IMAGES = [ - pythonLogoUrl, - liteLLMLogoUrl, - openAILogoUrl, - ragasLogoUrl, - langChainLogoUrl, -]; +import GetStarted from "@/components/pages/GetStartedPage/GetStarted"; const GetStartedPage = () => { - const workspaceName = useAppStore((state) => state.activeWorkspaceName); const { data: user } = useUser(); if (!user) return; - - const apiKey = user.apiKeys[0]; - - return ( -
    -
    -
    -
    -
    -
    - Welcome {user.userName} -
    - -

    - Get Started with Opik -

    -
    - - - - -
    - -
    - Start with one of our integrations or a few lines of code to log, - view and evaluate your LLM traces during both development and - production. -
    -
    - -
    -
    -
    -
    -
    - Integrate Opik -
    -
    - A step by step guide for adding Opik into your existing - application. Includes detailed examples. -
    - -
    - -
    - {LOGO_IMAGES.map((url) => { - return ; - })} -
    -
    - -
    -
    -
    - Copy your API key -
    -
    - API keys are used to send traces to the Opik platform. -
    -
    - -
    - - - -
    -
    -
    - -
    -
    Explore demo project
    - - - -
    -
    -
    - SQL query generation -
    -
    - Perform a text to SQL query generation using LangChain. The - example uses the Chinook database of a music store, with - both employee, customer and invoice data. -
    -
    -
    - Explore project -
    -
    - -
    -
    -
    -
    - ); + return ; }; export default GetStartedPage; diff --git a/apps/opik-frontend/src/plugins/comet/QuickstartPage.tsx b/apps/opik-frontend/src/plugins/comet/QuickstartPage.tsx new file mode 100644 index 000000000..68c483c97 --- /dev/null +++ b/apps/opik-frontend/src/plugins/comet/QuickstartPage.tsx @@ -0,0 +1,11 @@ +import useUser from "./useUser"; +import Quickstart from "@/components/pages/QuickstartPage/Quickstart"; + +const QuickstartPage = () => { + const { data: user } = useUser(); + + if (!user) return; + return ; +}; + +export default QuickstartPage; diff --git a/apps/opik-frontend/src/plugins/comet/UserMenu.tsx b/apps/opik-frontend/src/plugins/comet/UserMenu.tsx index 7aebc8235..ab7c0f5cf 100644 --- a/apps/opik-frontend/src/plugins/comet/UserMenu.tsx +++ b/apps/opik-frontend/src/plugins/comet/UserMenu.tsx @@ -275,10 +275,10 @@ const UserMenu = () => { - + - Get started guide + Quickstart guide diff --git a/apps/opik-frontend/src/router.tsx b/apps/opik-frontend/src/router.tsx index a28e37b5e..0ad731447 100644 --- a/apps/opik-frontend/src/router.tsx +++ b/apps/opik-frontend/src/router.tsx @@ -15,6 +15,7 @@ import ExperimentsPage from "@/components/pages/ExperimentsPage/ExperimentsPage" import CompareExperimentsPage from "@/components/pages/CompareExperimentsPage/CompareExperimentsPage"; import FeedbackDefinitionsPage from "@/components/pages/FeedbackDefinitionsPage/FeedbackDefinitionsPage"; import GetStartedPage from "@/components/pages/GetStartedPage/GetStartedPage"; +import QuickstartPage from "@/components/pages/QuickstartPage/QuickstartPage"; import HomePage from "@/components/pages/HomePage/HomePage"; import PartialPageLayout from "@/components/layout/PartialPageLayout/PartialPageLayout"; import ProjectPage from "@/components/pages/ProjectPage/ProjectPage"; @@ -66,6 +67,13 @@ const workspaceRoute = createRoute({ component: WorkspacePage, }); +// ----------- quickstart +const quickstartRoute = createRoute({ + path: "/$workspaceName/quickstart", + getParentRoute: () => workspaceGuardPartialLayoutRoute, + component: QuickstartPage, +}); + // ----------- get started const getStartedRoute = createRoute({ path: "/$workspaceName/get-started", @@ -177,7 +185,10 @@ const datasetItemsRoute = createRoute({ }); const routeTree = rootRoute.addChildren([ - workspaceGuardPartialLayoutRoute.addChildren([getStartedRoute]), + workspaceGuardPartialLayoutRoute.addChildren([ + getStartedRoute, + quickstartRoute, + ]), workspaceGuardRoute.addChildren([ homeRoute, workspaceRoute.addChildren([ diff --git a/apps/opik-frontend/src/store/PluginsStore.ts b/apps/opik-frontend/src/store/PluginsStore.ts index d23502037..c6fd0465c 100644 --- a/apps/opik-frontend/src/store/PluginsStore.ts +++ b/apps/opik-frontend/src/store/PluginsStore.ts @@ -7,6 +7,7 @@ type PluginStore = { Logo: React.ComponentType<{ expanded: boolean }> | null; UserMenu: React.ComponentType | null; GetStartedPage: React.ComponentType | null; + QuickstartPage: React.ComponentType | null; WorkspacePreloader: React.ComponentType<{ children: React.ReactNode }> | null; init: unknown; setupPlugins: (folderName: string) => Promise; @@ -17,6 +18,7 @@ const PLUGIN_NAMES = [ "Logo", "UserMenu", "GetStartedPage", + "QuickstartPage", "WorkspacePreloader", "init", ]; @@ -25,6 +27,7 @@ const usePluginsStore = create((set) => ({ Logo: null, UserMenu: null, GetStartedPage: null, + QuickstartPage: null, WorkspacePreloader: null, init: null, setupPlugins: async (folderName: string) => {