From ba5b38e7ac7e55ee40fe8e4c2a5da189869c6540 Mon Sep 17 00:00:00 2001 From: sasha Date: Tue, 21 Jan 2025 12:38:15 +0100 Subject: [PATCH] [OPIK-711]: playground Anthropic integration (#1098) * [OPIK-711] [OPIK-712]: add integrations to gemini and anthropic on the FE; * [OPIK-711] [OPIK-712]: support the last picked model; * [OPIK-711] [OPIK-712]: eslint issues; * [OPIK-711] [OPIK-712]: rename models; * [NA]: remove gemini; --------- Co-authored-by: Sasha --- .../PromptModelSelect/PromptModelSelect.tsx | 34 ++++++---- .../PromptModelConfigs.tsx | 11 +++ .../providerConfigs/AnthropicModelConfigs.tsx | 64 +++++++++++++++++ .../AIProvidersTab/AIProviderCell.tsx | 6 +- .../AIProvidersRowActionsCell.tsx | 2 +- .../PlaygroundPrompts/PlaygroundPrompt.tsx | 5 +- .../PlaygroundPrompts/PlaygroundPrompts.tsx | 15 +++- .../PlaygroundPrompts/useLastPickedModel.tsx | 15 ++++ .../AddEditAIProviderDialog.tsx | 6 +- apps/opik-frontend/src/constants/llm.ts | 45 ++++++++++++ apps/opik-frontend/src/constants/providers.ts | 10 +++ .../src/icons/integrations/anthropic.svg | 6 ++ apps/opik-frontend/src/lib/playground.ts | 68 ++++++++++++++++--- apps/opik-frontend/src/types/providers.ts | 29 ++++++-- 14 files changed, 274 insertions(+), 42 deletions(-) create mode 100644 apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/providerConfigs/AnthropicModelConfigs.tsx create mode 100644 apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel.tsx create mode 100644 apps/opik-frontend/src/icons/integrations/anthropic.svg diff --git a/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSelect/PromptModelSelect.tsx b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSelect/PromptModelSelect.tsx index eebeba7c35..6913140f5a 100644 --- a/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSelect/PromptModelSelect.tsx +++ b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSelect/PromptModelSelect.tsx @@ -76,24 +76,28 @@ const PromptModelSelect = ({ configuredProviderKeys, ); - return Object.entries(filteredByConfiguredProviders).map( - ([pn, providerModels]) => { + return Object.entries(filteredByConfiguredProviders) + .map(([pn, providerModels]) => { const providerName = pn as PROVIDER_TYPE; + const options = providerModels + .filter((m) => (onlyWithStructuredOutput ? m.structuredOutput : true)) + .map((providerModel) => ({ + label: providerModel.label, + value: providerModel.value, + })); + + if (!options.length) { + return null; + } + return { label: PROVIDERS[providerName].label, - options: providerModels - .filter((m) => - onlyWithStructuredOutput ? m.structuredOutput : true, - ) - .map((providerModel) => ({ - label: providerModel.label, - value: providerModel.value, - })), + options, icon: PROVIDERS[providerName].icon, }; - }, - ); + }) + .filter((g): g is NonNullable => !isNull(g)); }, [configuredProviderKeys, onlyWithStructuredOutput]); const filteredOptions = useMemo(() => { @@ -241,7 +245,7 @@ const PromptModelSelect = ({ return null; } - return ; + return ; }; return ( @@ -258,7 +262,9 @@ const PromptModelSelect = ({ >
{renderProviderValueIcon()} - {provider && PROVIDERS[provider].label} {value} + + {provider && PROVIDERS[provider].label} {value} +
diff --git a/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/PromptModelConfigs.tsx b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/PromptModelConfigs.tsx index c609ebee7a..0072790a63 100644 --- a/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/PromptModelConfigs.tsx +++ b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/PromptModelConfigs.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Settings2 } from "lucide-react"; import { + LLMAnthropicConfigsType, LLMOpenAIConfigsType, LLMPromptConfigsType, PROVIDER_TYPE, @@ -15,6 +16,7 @@ import { import { Button } from "@/components/ui/button"; import OpenAIModelConfigs from "@/components/pages-shared/llm/PromptModelSettings/providerConfigs/OpenAIModelConfigs"; +import AnthropicModelConfigs from "@/components/pages-shared/llm/PromptModelSettings/providerConfigs/AnthropicModelConfigs"; interface PromptModelConfigsProps { provider: PROVIDER_TYPE | ""; @@ -39,6 +41,15 @@ const PromptModelConfigs = ({ ); } + if (provider === PROVIDER_TYPE.ANTHROPIC) { + return ( + + ); + } + return; }; diff --git a/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/providerConfigs/AnthropicModelConfigs.tsx b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/providerConfigs/AnthropicModelConfigs.tsx new file mode 100644 index 0000000000..36b0dfac0a --- /dev/null +++ b/apps/opik-frontend/src/components/pages-shared/llm/PromptModelSettings/providerConfigs/AnthropicModelConfigs.tsx @@ -0,0 +1,64 @@ +import React from "react"; + +import SliderInputControl from "@/components/shared/SliderInputControl/SliderInputControl"; +import { LLMAnthropicConfigsType } from "@/types/providers"; +import { DEFAULT_ANTHROPIC_CONFIGS } from "@/constants/llm"; +import PromptModelConfigsTooltipContent from "@/components/pages-shared/llm/PromptModelSettings/providerConfigs/PromptModelConfigsTooltipContent"; + +interface AnthropicModelConfigsProps { + configs: LLMAnthropicConfigsType; + onChange: (configs: Partial) => void; +} + +const AnthropicModelConfigs = ({ + configs, + onChange, +}: AnthropicModelConfigsProps) => { + return ( +
+ onChange({ temperature: v })} + id="temperature" + min={0} + max={1} + step={0.01} + defaultValue={DEFAULT_ANTHROPIC_CONFIGS.TEMPERATURE} + label="Temperature" + tooltip={ + + } + /> + + onChange({ maxCompletionTokens: v })} + id="maxCompletionTokens" + min={0} + max={10000} + step={1} + defaultValue={DEFAULT_ANTHROPIC_CONFIGS.MAX_COMPLETION_TOKENS} + label="Max output tokens" + tooltip={ + + } + /> + + onChange({ topP: v })} + id="topP" + min={0} + max={1} + step={0.01} + defaultValue={DEFAULT_ANTHROPIC_CONFIGS.TOP_P} + label="Top P" + tooltip={ + + } + /> +
+ ); +}; + +export default AnthropicModelConfigs; diff --git a/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProviderCell.tsx b/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProviderCell.tsx index 474e225bf1..a2702e246f 100644 --- a/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProviderCell.tsx +++ b/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProviderCell.tsx @@ -7,9 +7,9 @@ import { PROVIDER_TYPE } from "@/types/providers"; const AIProviderCell = (context: CellContext) => { const provider = context.getValue(); - const Icon = PROVIDERS[provider].icon; + const Icon = PROVIDERS[provider]?.icon || null; - const providerKeyLabel = PROVIDERS[provider].label; + const providerKeyLabel = PROVIDERS[provider]?.label || ""; return ( ) => { tableMetadata={context.table.options.meta} className="flex gap-1" > - + {Icon && } {providerKeyLabel} ); diff --git a/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProvidersRowActionsCell.tsx b/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProvidersRowActionsCell.tsx index 1141b02d66..c05b1fefa8 100644 --- a/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProvidersRowActionsCell.tsx +++ b/apps/opik-frontend/src/components/pages/ConfigurationPage/AIProvidersTab/AIProvidersRowActionsCell.tsx @@ -48,7 +48,7 @@ const AIProvidersRowActionsCell: React.FunctionComponent< open={open === 1} setOpen={setOpen} onConfirm={deleteProviderKeyHandler} - title={`Delete ${PROVIDERS[providerKey.provider].label} configuration`} + title={`Delete ${PROVIDERS[providerKey.provider]?.label} configuration`} description="Are you sure you want to delete this provider configuration?" confirmText="Delete configuration" /> diff --git a/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompt.tsx b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompt.tsx index 4bc5ad3bba..3d1332b817 100644 --- a/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompt.tsx +++ b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompt.tsx @@ -34,6 +34,7 @@ import { } from "@/store/PlaygroundStore"; import { getDefaultProviderKey } from "@/lib/provider"; import { PROVIDERS } from "@/constants/providers"; +import useLastPickedModel from "@/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel"; interface PlaygroundPromptProps { workspaceName: string; @@ -53,6 +54,7 @@ const PlaygroundPrompt = ({ const checkedIfModelIsValidRef = useRef(false); const prompt = usePromptById(promptId); + const [, setLastPickedModel] = useLastPickedModel(); const { model, messages, configs, name } = prompt; @@ -107,8 +109,9 @@ const PlaygroundPrompt = ({ const handleUpdateModel = useCallback( (model: PROVIDER_MODEL_TYPE) => { updatePrompt(promptId, { model }); + setLastPickedModel(model); }, - [updatePrompt, promptId], + [updatePrompt, promptId, setLastPickedModel], ); const handleAddProvider = useCallback( diff --git a/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompts.tsx b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompts.tsx index ebffa1775b..3d25de4ea8 100644 --- a/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompts.tsx +++ b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/PlaygroundPrompts.tsx @@ -10,6 +10,7 @@ import { usePromptIds, useSetPromptMap, } from "@/store/PlaygroundStore"; +import useLastPickedModel from "@/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel"; interface PlaygroundPromptsState { workspaceName: string; @@ -28,15 +29,23 @@ const PlaygroundPrompts = ({ const promptIds = usePromptIds(); + const [lastPickedModel] = useLastPickedModel(); + const handleAddPrompt = () => { - const newPrompt = generateDefaultPrompt({ setupProviders: providerKeys }); + const newPrompt = generateDefaultPrompt({ + setupProviders: providerKeys, + lastPickedModel, + }); addPrompt(newPrompt); }; const resetPlayground = useCallback(() => { - const newPrompt = generateDefaultPrompt({ setupProviders: providerKeys }); + const newPrompt = generateDefaultPrompt({ + setupProviders: providerKeys, + lastPickedModel, + }); setPromptMap([newPrompt.id], { [newPrompt.id]: newPrompt }); - }, [setPromptMap, providerKeys]); + }, [setPromptMap, providerKeys, lastPickedModel]); useEffect(() => { // hasn't been initialized yet or the last prompt is removed diff --git a/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel.tsx b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel.tsx new file mode 100644 index 0000000000..314bf2b287 --- /dev/null +++ b/apps/opik-frontend/src/components/pages/PlaygroundPage/PlaygroundPrompts/useLastPickedModel.tsx @@ -0,0 +1,15 @@ +import useLocalStorageState from "use-local-storage-state"; +import { PROVIDER_MODEL_TYPE } from "@/types/providers"; + +const PLAYGROUND_LAST_PICKED_MODEL = "playground-last-picked-model"; + +const useLastPickedModel = () => { + return useLocalStorageState( + PLAYGROUND_LAST_PICKED_MODEL, + { + defaultValue: "", + }, + ); +}; + +export default useLastPickedModel; diff --git a/apps/opik-frontend/src/components/shared/AddEditAIProviderDialog/AddEditAIProviderDialog.tsx b/apps/opik-frontend/src/components/shared/AddEditAIProviderDialog/AddEditAIProviderDialog.tsx index d54d81a305..47ac3aef21 100644 --- a/apps/opik-frontend/src/components/shared/AddEditAIProviderDialog/AddEditAIProviderDialog.tsx +++ b/apps/opik-frontend/src/components/shared/AddEditAIProviderDialog/AddEditAIProviderDialog.tsx @@ -42,7 +42,7 @@ const AddEditAIProviderDialog: React.FC = ({ const isEdit = Boolean(providerKey); const isValid = Boolean(apiKey.length); - const providerName = (provider && PROVIDERS[provider].label) || ""; + const providerName = (provider && PROVIDERS[provider]?.label) || ""; const title = isEdit ? "Edit AI provider configuration" @@ -83,7 +83,7 @@ const AddEditAIProviderDialog: React.FC = ({ ]); const renderOption = (option: DropdownOption) => { - const Icon = PROVIDERS[option.value as PROVIDER_TYPE].icon; + const Icon = PROVIDERS[option.value as PROVIDER_TYPE]?.icon; return ( @@ -125,7 +125,7 @@ const AddEditAIProviderDialog: React.FC = ({ Get your {providerName} API key{" "}