mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
Add new integration with Opik Tracking tool (#11501)
This commit is contained in:
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import TracingIcon from './tracing-icon'
|
||||
import ProviderPanel from './provider-panel'
|
||||
import type { LangFuseConfig, LangSmithConfig } from './type'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
|
||||
import { TracingProvider } from './type'
|
||||
import ProviderConfigModal from './provider-config-modal'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
@ -23,7 +23,8 @@ export type PopupProps = {
|
||||
onChooseProvider: (provider: TracingProvider) => void
|
||||
langSmithConfig: LangSmithConfig | null
|
||||
langFuseConfig: LangFuseConfig | null
|
||||
onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig) => void
|
||||
opikConfig: OpikConfig | null
|
||||
onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void
|
||||
onConfigRemoved: (provider: TracingProvider) => void
|
||||
}
|
||||
|
||||
@ -36,6 +37,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
onChooseProvider,
|
||||
langSmithConfig,
|
||||
langFuseConfig,
|
||||
opikConfig,
|
||||
onConfigUpdated,
|
||||
onConfigRemoved,
|
||||
}) => {
|
||||
@ -59,7 +61,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
}
|
||||
}, [onChooseProvider])
|
||||
|
||||
const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig) => {
|
||||
const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig) => {
|
||||
onConfigUpdated(currentProvider!, payload)
|
||||
hideConfigModal()
|
||||
}, [currentProvider, hideConfigModal, onConfigUpdated])
|
||||
@ -69,8 +71,8 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
hideConfigModal()
|
||||
}, [currentProvider, hideConfigModal, onConfigRemoved])
|
||||
|
||||
const providerAllConfigured = langSmithConfig && langFuseConfig
|
||||
const providerAllNotConfigured = !langSmithConfig && !langFuseConfig
|
||||
const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig
|
||||
const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig
|
||||
|
||||
const switchContent = (
|
||||
<Switch
|
||||
@ -90,6 +92,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
onConfig={handleOnConfig(TracingProvider.langSmith)}
|
||||
isChosen={chosenProvider === TracingProvider.langSmith}
|
||||
onChoose={handleOnChoose(TracingProvider.langSmith)}
|
||||
key="langSmith-provider-panel"
|
||||
/>
|
||||
)
|
||||
|
||||
@ -102,9 +105,61 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
onConfig={handleOnConfig(TracingProvider.langfuse)}
|
||||
isChosen={chosenProvider === TracingProvider.langfuse}
|
||||
onChoose={handleOnChoose(TracingProvider.langfuse)}
|
||||
key="langfuse-provider-panel"
|
||||
/>
|
||||
)
|
||||
|
||||
const opikPanel = (
|
||||
<ProviderPanel
|
||||
type={TracingProvider.opik}
|
||||
readOnly={readOnly}
|
||||
config={opikConfig}
|
||||
hasConfigured={!!opikConfig}
|
||||
onConfig={handleOnConfig(TracingProvider.opik)}
|
||||
isChosen={chosenProvider === TracingProvider.opik}
|
||||
onChoose={handleOnChoose(TracingProvider.opik)}
|
||||
key="opik-provider-panel"
|
||||
/>
|
||||
)
|
||||
|
||||
const configuredProviderPanel = () => {
|
||||
const configuredPanels: ProviderPanel[] = []
|
||||
|
||||
if (langSmithConfig)
|
||||
configuredPanels.push(langSmithPanel)
|
||||
|
||||
if (langFuseConfig)
|
||||
configuredPanels.push(langfusePanel)
|
||||
|
||||
if (opikConfig)
|
||||
configuredPanels.push(opikPanel)
|
||||
|
||||
return configuredPanels
|
||||
}
|
||||
|
||||
const moreProviderPanel = () => {
|
||||
const notConfiguredPanels: ProviderPanel[] = []
|
||||
|
||||
if (!langSmithConfig)
|
||||
notConfiguredPanels.push(langSmithPanel)
|
||||
|
||||
if (!langFuseConfig)
|
||||
notConfiguredPanels.push(langfusePanel)
|
||||
|
||||
if (!opikConfig)
|
||||
notConfiguredPanels.push(opikPanel)
|
||||
|
||||
return notConfiguredPanels
|
||||
}
|
||||
|
||||
const configuredProviderConfig = () => {
|
||||
if (currentProvider === TracingProvider.langSmith)
|
||||
return langSmithConfig
|
||||
if (currentProvider === TracingProvider.langfuse)
|
||||
return langFuseConfig
|
||||
return opikConfig
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-[420px] p-4 rounded-2xl bg-white border-[0.5px] border-black/5 shadow-lg'>
|
||||
<div className='flex justify-between items-center'>
|
||||
@ -146,18 +201,19 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
<div className='mt-2 space-y-2'>
|
||||
{langSmithPanel}
|
||||
{langfusePanel}
|
||||
{opikPanel}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div>
|
||||
<div className='mt-2'>
|
||||
{langSmithConfig ? langSmithPanel : langfusePanel}
|
||||
<div className='mt-2 space-y-2'>
|
||||
{configuredProviderPanel()}
|
||||
</div>
|
||||
<div className='mt-3 leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||
<div className='mt-2'>
|
||||
{!langSmithConfig ? langSmithPanel : langfusePanel}
|
||||
<div className='mt-2 space-y-2'>
|
||||
{moreProviderPanel()}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -167,7 +223,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
<ProviderConfigModal
|
||||
appId={appId}
|
||||
type={currentProvider!}
|
||||
payload={currentProvider === TracingProvider.langSmith ? langSmithConfig : langFuseConfig}
|
||||
payload={configuredProviderConfig()}
|
||||
onCancel={hideConfigModal}
|
||||
onSaved={handleConfigUpdated}
|
||||
onChosen={onChooseProvider}
|
||||
|
||||
@ -3,4 +3,5 @@ import { TracingProvider } from './type'
|
||||
export const docURL = {
|
||||
[TracingProvider.langSmith]: 'https://docs.smith.langchain.com/',
|
||||
[TracingProvider.langfuse]: 'https://docs.langfuse.com',
|
||||
[TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions',
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import { TracingProvider } from './type'
|
||||
import TracingIcon from './tracing-icon'
|
||||
import ConfigButton from './config-button'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LangfuseIcon, LangsmithIcon } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { LangfuseIcon, LangsmithIcon, OpikIcon } from '@/app/components/base/icons/src/public/tracing'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps'
|
||||
import type { TracingStatus } from '@/models/app'
|
||||
@ -70,11 +70,20 @@ const Panel: FC = () => {
|
||||
})
|
||||
}
|
||||
const inUseTracingProvider: TracingProvider | null = tracingStatus?.tracing_provider || null
|
||||
const InUseProviderIcon = inUseTracingProvider === TracingProvider.langSmith ? LangsmithIcon : LangfuseIcon
|
||||
|
||||
const InUseProviderIcon
|
||||
= inUseTracingProvider === TracingProvider.langSmith
|
||||
? LangsmithIcon
|
||||
: inUseTracingProvider === TracingProvider.langfuse
|
||||
? LangfuseIcon
|
||||
: inUseTracingProvider === TracingProvider.opik
|
||||
? OpikIcon
|
||||
: null
|
||||
|
||||
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
|
||||
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
|
||||
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig)
|
||||
const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null)
|
||||
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig)
|
||||
|
||||
const fetchTracingConfig = async () => {
|
||||
const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith })
|
||||
@ -83,6 +92,9 @@ const Panel: FC = () => {
|
||||
const { tracing_config: langFuseConfig, has_not_configured: langFuseHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langfuse })
|
||||
if (!langFuseHasNotConfig)
|
||||
setLangFuseConfig(langFuseConfig as LangFuseConfig)
|
||||
const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik })
|
||||
if (!OpikHasNotConfig)
|
||||
setOpikConfig(opikConfig as OpikConfig)
|
||||
}
|
||||
|
||||
const handleTracingConfigUpdated = async (provider: TracingProvider) => {
|
||||
@ -90,15 +102,19 @@ const Panel: FC = () => {
|
||||
const { tracing_config } = await doFetchTracingConfig({ appId, provider })
|
||||
if (provider === TracingProvider.langSmith)
|
||||
setLangSmithConfig(tracing_config as LangSmithConfig)
|
||||
else
|
||||
else if (provider === TracingProvider.langSmith)
|
||||
setLangFuseConfig(tracing_config as LangFuseConfig)
|
||||
else if (provider === TracingProvider.opik)
|
||||
setOpikConfig(tracing_config as OpikConfig)
|
||||
}
|
||||
|
||||
const handleTracingConfigRemoved = (provider: TracingProvider) => {
|
||||
if (provider === TracingProvider.langSmith)
|
||||
setLangSmithConfig(null)
|
||||
else
|
||||
else if (provider === TracingProvider.langSmith)
|
||||
setLangFuseConfig(null)
|
||||
else if (provider === TracingProvider.opik)
|
||||
setOpikConfig(null)
|
||||
if (provider === inUseTracingProvider) {
|
||||
handleTracingStatusChange({
|
||||
enabled: false,
|
||||
@ -167,6 +183,7 @@ const Panel: FC = () => {
|
||||
onChooseProvider={handleChooseProvider}
|
||||
langSmithConfig={langSmithConfig}
|
||||
langFuseConfig={langFuseConfig}
|
||||
opikConfig={opikConfig}
|
||||
onConfigUpdated={handleTracingConfigUpdated}
|
||||
onConfigRemoved={handleTracingConfigRemoved}
|
||||
controlShowPopup={controlShowPopup}
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Field from './field'
|
||||
import type { LangFuseConfig, LangSmithConfig } from './type'
|
||||
import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type'
|
||||
import { TracingProvider } from './type'
|
||||
import { docURL } from './config'
|
||||
import {
|
||||
@ -21,10 +21,10 @@ import Toast from '@/app/components/base/toast'
|
||||
type Props = {
|
||||
appId: string
|
||||
type: TracingProvider
|
||||
payload?: LangSmithConfig | LangFuseConfig | null
|
||||
payload?: LangSmithConfig | LangFuseConfig | OpikConfig | null
|
||||
onRemoved: () => void
|
||||
onCancel: () => void
|
||||
onSaved: (payload: LangSmithConfig | LangFuseConfig) => void
|
||||
onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void
|
||||
onChosen: (provider: TracingProvider) => void
|
||||
}
|
||||
|
||||
@ -42,6 +42,13 @@ const langFuseConfigTemplate = {
|
||||
host: '',
|
||||
}
|
||||
|
||||
const opikConfigTemplate = {
|
||||
api_key: '',
|
||||
project: '',
|
||||
url: '',
|
||||
workspace: '',
|
||||
}
|
||||
|
||||
const ProviderConfigModal: FC<Props> = ({
|
||||
appId,
|
||||
type,
|
||||
@ -55,14 +62,17 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
const isEdit = !!payload
|
||||
const isAdd = !isEdit
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig>((() => {
|
||||
const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig>((() => {
|
||||
if (isEdit)
|
||||
return payload
|
||||
|
||||
if (type === TracingProvider.langSmith)
|
||||
return langSmithConfigTemplate
|
||||
|
||||
return langFuseConfigTemplate
|
||||
else if (type === TracingProvider.langfuse)
|
||||
return langFuseConfigTemplate
|
||||
|
||||
return opikConfigTemplate
|
||||
})())
|
||||
const [isShowRemoveConfirm, {
|
||||
setTrue: showRemoveConfirm,
|
||||
@ -111,6 +121,10 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Host' })
|
||||
}
|
||||
|
||||
if (type === TracingProvider.opik) {
|
||||
const postData = config as OpikConfig
|
||||
}
|
||||
|
||||
return errorMessage
|
||||
}, [config, t, type])
|
||||
const handleSave = useCallback(async () => {
|
||||
@ -215,6 +229,38 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{type === TracingProvider.opik && (
|
||||
<>
|
||||
<Field
|
||||
label='API Key'
|
||||
labelClassName='!text-sm'
|
||||
value={(config as OpikConfig).api_key}
|
||||
onChange={handleConfigChange('api_key')}
|
||||
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'API Key' })!}
|
||||
/>
|
||||
<Field
|
||||
label={t(`${I18N_PREFIX}.project`)!}
|
||||
labelClassName='!text-sm'
|
||||
value={(config as OpikConfig).project}
|
||||
onChange={handleConfigChange('project')}
|
||||
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.project`) })!}
|
||||
/>
|
||||
<Field
|
||||
label='Workspace'
|
||||
labelClassName='!text-sm'
|
||||
value={(config as OpikConfig).workspace}
|
||||
onChange={handleConfigChange('workspace')}
|
||||
placeholder={'default'}
|
||||
/>
|
||||
<Field
|
||||
label='Url'
|
||||
labelClassName='!text-sm'
|
||||
value={(config as OpikConfig).url}
|
||||
onChange={handleConfigChange('url')}
|
||||
placeholder={'https://www.comet.com/opik/api/'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className='my-8 flex justify-between items-center h-8'>
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TracingProvider } from './type'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { LangfuseIconBig, LangsmithIconBig, OpikIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
@ -24,6 +24,7 @@ const getIcon = (type: TracingProvider) => {
|
||||
return ({
|
||||
[TracingProvider.langSmith]: LangsmithIconBig,
|
||||
[TracingProvider.langfuse]: LangfuseIconBig,
|
||||
[TracingProvider.opik]: OpikIconBig,
|
||||
})[type]
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export enum TracingProvider {
|
||||
langSmith = 'langsmith',
|
||||
langfuse = 'langfuse',
|
||||
opik = 'opik',
|
||||
}
|
||||
|
||||
export type LangSmithConfig = {
|
||||
@ -14,3 +15,10 @@ export type LangFuseConfig = {
|
||||
secret_key: string
|
||||
host: string
|
||||
}
|
||||
|
||||
export type OpikConfig = {
|
||||
api_key: string
|
||||
project: string
|
||||
workspace: string
|
||||
url: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user