diff --git a/docs/docs/guides/evaluation/scorers.md b/docs/docs/guides/evaluation/scorers.md index e313dcb5582..233d162b15c 100644 --- a/docs/docs/guides/evaluation/scorers.md +++ b/docs/docs/guides/evaluation/scorers.md @@ -455,7 +455,7 @@ In Weave, Scorers are used to evaluate AI outputs and return evaluation metrics. from weave.scorers import OpenAIModerationScorer from openai import OpenAI - oai_client = OpenAI(api_key=...) # initialize your LLM client here + oai_client = OpenAI() # initialize your LLM client here scorer = OpenAIModerationScorer( client=oai_client, diff --git a/docs/docs/guides/integrations/local_models.md b/docs/docs/guides/integrations/local_models.md index 090d22a3f76..2597ad19dd1 100644 --- a/docs/docs/guides/integrations/local_models.md +++ b/docs/docs/guides/integrations/local_models.md @@ -14,7 +14,6 @@ First and most important, is the `base_url` change during the `openai.OpenAI()` ```python client = openai.OpenAI( - api_key='fake', base_url="http://localhost:1234", ) ``` diff --git a/docs/docs/guides/integrations/notdiamond.md b/docs/docs/guides/integrations/notdiamond.md index 3106ef11f1b..e98a23d1334 100644 --- a/docs/docs/guides/integrations/notdiamond.md +++ b/docs/docs/guides/integrations/notdiamond.md @@ -68,7 +68,6 @@ preference_id = train_router( response_column="actual", language="en", maximize=True, - api_key=api_key, ) ``` diff --git a/docs/docs/quickstart.md b/docs/docs/quickstart.md index 59ac16ef236..65bc813af03 100644 --- a/docs/docs/quickstart.md +++ b/docs/docs/quickstart.md @@ -50,7 +50,7 @@ _In this example, we're using openai so you will need to add an OpenAI [API key] import weave from openai import OpenAI - client = OpenAI(api_key="...") + client = OpenAI() # Weave will track the inputs, outputs and code of this function # highlight-next-line diff --git a/docs/docs/tutorial-tracing_2.md b/docs/docs/tutorial-tracing_2.md index 719ee99e012..d6e392d9c56 100644 --- a/docs/docs/tutorial-tracing_2.md +++ b/docs/docs/tutorial-tracing_2.md @@ -24,7 +24,7 @@ Building on our [basic tracing example](/quickstart), we will now add additional import json from openai import OpenAI - client = OpenAI(api_key="...") + client = OpenAI() # highlight-next-line @weave.op() diff --git a/weave-js/src/components/ErrorBoundary.tsx b/weave-js/src/components/ErrorBoundary.tsx index 48b4fcf1a1e..053af08aa97 100644 --- a/weave-js/src/components/ErrorBoundary.tsx +++ b/weave-js/src/components/ErrorBoundary.tsx @@ -1,6 +1,7 @@ import {datadogRum} from '@datadog/browser-rum'; import * as Sentry from '@sentry/react'; import React, {Component, ErrorInfo, ReactNode} from 'react'; +import {v7 as uuidv7} from 'uuid'; import {weaveErrorToDDPayload} from '../errors'; import {ErrorPanel} from './ErrorPanel'; @@ -10,24 +11,31 @@ type Props = { }; type State = { - hasError: boolean; + uuid: string | undefined; + timestamp: Date | undefined; + error: Error | undefined; }; export class ErrorBoundary extends Component { - public static getDerivedStateFromError(_: Error): State { - return {hasError: true}; + public static getDerivedStateFromError(error: Error): State { + return {uuid: uuidv7(), timestamp: new Date(), error}; } public state: State = { - hasError: false, + uuid: undefined, + timestamp: undefined, + error: undefined, }; public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + const {uuid} = this.state; datadogRum.addAction( 'weave_panel_error_boundary', - weaveErrorToDDPayload(error) + weaveErrorToDDPayload(error, undefined, uuid) ); - Sentry.captureException(error, { + extra: { + uuid, + }, tags: { weaveErrorBoundary: 'true', }, @@ -35,8 +43,9 @@ export class ErrorBoundary extends Component { } public render() { - if (this.state.hasError) { - return ; + const {uuid, timestamp, error} = this.state; + if (error != null) { + return ; } return this.props.children; diff --git a/weave-js/src/components/ErrorPanel.tsx b/weave-js/src/components/ErrorPanel.tsx index cbcfd244ac9..86945922150 100644 --- a/weave-js/src/components/ErrorPanel.tsx +++ b/weave-js/src/components/ErrorPanel.tsx @@ -1,7 +1,19 @@ -import React, {forwardRef, useEffect, useRef, useState} from 'react'; +import copyToClipboard from 'copy-to-clipboard'; +import _ from 'lodash'; +import React, { + forwardRef, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import styled from 'styled-components'; +import {toast} from '../common/components/elements/Toast'; import {hexToRGB, MOON_300, MOON_600} from '../common/css/globals.styles'; +import {useViewerInfo} from '../common/hooks/useViewerInfo'; +import {getCookieBool, getFirebaseCookie} from '../common/util/cookie'; +import {Button} from './Button'; import {Icon} from './Icon'; import {Tooltip} from './Tooltip'; @@ -14,6 +26,11 @@ type ErrorPanelProps = { title?: string; subtitle?: string; subtitle2?: string; + + // These props are for error details object + uuid?: string; + timestamp?: Date; + error?: Error; }; export const Centered = styled.div` @@ -85,11 +102,87 @@ export const ErrorPanelSmall = ({ ); }; +const getDateObject = (timestamp?: Date): Record | null => { + if (!timestamp) { + return null; + } + return { + // e.g. "2024-12-12T06:10:19.475Z", + iso: timestamp.toISOString(), + // e.g. "Thursday, December 12, 2024 at 6:10:19 AM Coordinated Universal Time" + long: timestamp.toLocaleString('en-US', { + weekday: 'long', + year: 'numeric', // Full year + month: 'long', // Full month name + day: 'numeric', // Day of the month + hour: 'numeric', // Hour (12-hour or 24-hour depending on locale) + minute: 'numeric', + second: 'numeric', + timeZone: 'UTC', // Ensures it's in UTC + timeZoneName: 'long', // Full time zone name + }), + user: timestamp.toLocaleString('en-US', { + dateStyle: 'full', + timeStyle: 'full', + }), + }; +}; + +const getErrorObject = (error?: Error): Record | null => { + if (!error) { + return null; + } + + // Error object properties are not enumerable so we have to copy them manually + const stack = (error.stack ?? '').split('\n'); + return { + message: error.message, + stack, + }; +}; + export const ErrorPanelLarge = forwardRef( - ({title, subtitle, subtitle2}, ref) => { + ({title, subtitle, subtitle2, uuid, timestamp, error}, ref) => { const titleStr = title ?? DEFAULT_TITLE; const subtitleStr = subtitle ?? DEFAULT_SUBTITLE; const subtitle2Str = subtitle2 ?? DEFAULT_SUBTITLE2; + + const {userInfo} = useViewerInfo(); + + const onClick = useCallback(() => { + const betaVersion = getFirebaseCookie('betaVersion'); + const isUsingAdminPrivileges = getCookieBool('use_admin_privileges'); + const {location, navigator, screen} = window; + const {userAgent, language} = navigator; + const details = { + uuid, + url: location.href, + error: getErrorObject(error), + timestamp_err: getDateObject(timestamp), + timestamp_copied: getDateObject(new Date()), + user: _.pick(userInfo, ['id', 'username']), // Skipping teams and admin + cookies: { + ...(betaVersion && {betaVersion}), + ...(isUsingAdminPrivileges && {use_admin_privileges: true}), + }, + browser: { + userAgent, + language, + screenSize: { + width: screen.width, + height: screen.height, + }, + viewportSize: { + width: window.innerWidth, + height: window.innerHeight, + }, + }, + }; + const detailsText = JSON.stringify(details, null, 2); + copyToClipboard(detailsText); + toast('Copied to clipboard'); + }, [uuid, timestamp, error, userInfo]); + return ( @@ -98,6 +191,14 @@ export const ErrorPanelLarge = forwardRef( {titleStr} {subtitleStr} {subtitle2Str} + ); } diff --git a/weave-js/src/errors.ts b/weave-js/src/errors.ts index daa3e0c97a0..f2582baade8 100644 --- a/weave-js/src/errors.ts +++ b/weave-js/src/errors.ts @@ -37,7 +37,8 @@ type DDErrorPayload = { export const weaveErrorToDDPayload = ( error: Error, - weave?: WeaveApp + weave?: WeaveApp, + uuid?: string ): DDErrorPayload => { try { return { @@ -49,6 +50,7 @@ export const weaveErrorToDDPayload = ( windowLocationURL: trimString(window.location.href), weaveContext: weave?.client.debugMeta(), isServerError: error instanceof UseNodeValueServerExecutionError, + ...(uuid != null && {uuid}), }; } catch (e) { // If we fail to serialize the error, just return an empty object.