feat: MLflow tracing (#26093)

Signed-off-by: B-Step62 <yuki.watanabe@databricks.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
This commit is contained in:
Yuki Watanabe
2025-11-22 14:53:58 +09:00
committed by GitHub
parent ea320ce055
commit c6e6f3b7cb
48 changed files with 1737 additions and 17 deletions

View File

@@ -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 { AliyunConfig, ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import { TracingProvider } from './type'
import ProviderConfigModal from './provider-config-modal'
import Indicator from '@/app/components/header/indicator'
@@ -30,8 +30,10 @@ export type PopupProps = {
opikConfig: OpikConfig | null
weaveConfig: WeaveConfig | null
aliyunConfig: AliyunConfig | null
mlflowConfig: MLflowConfig | null
databricksConfig: DatabricksConfig | null
tencentConfig: TencentConfig | null
onConfigUpdated: (provider: TracingProvider, payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig) => void
onConfigUpdated: (provider: TracingProvider, payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig | MLflowConfig | DatabricksConfig) => void
onConfigRemoved: (provider: TracingProvider) => void
}
@@ -49,6 +51,8 @@ const ConfigPopup: FC<PopupProps> = ({
opikConfig,
weaveConfig,
aliyunConfig,
mlflowConfig,
databricksConfig,
tencentConfig,
onConfigUpdated,
onConfigRemoved,
@@ -73,7 +77,7 @@ const ConfigPopup: FC<PopupProps> = ({
}
}, [onChooseProvider])
const handleConfigUpdated = useCallback((payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig) => {
const handleConfigUpdated = useCallback((payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | MLflowConfig | DatabricksConfig | TencentConfig) => {
onConfigUpdated(currentProvider!, payload)
hideConfigModal()
}, [currentProvider, hideConfigModal, onConfigUpdated])
@@ -83,8 +87,8 @@ const ConfigPopup: FC<PopupProps> = ({
hideConfigModal()
}, [currentProvider, hideConfigModal, onConfigRemoved])
const providerAllConfigured = arizeConfig && phoenixConfig && langSmithConfig && langFuseConfig && opikConfig && weaveConfig && aliyunConfig && tencentConfig
const providerAllNotConfigured = !arizeConfig && !phoenixConfig && !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig && !aliyunConfig && !tencentConfig
const providerAllConfigured = arizeConfig && phoenixConfig && langSmithConfig && langFuseConfig && opikConfig && weaveConfig && aliyunConfig && mlflowConfig && databricksConfig && tencentConfig
const providerAllNotConfigured = !arizeConfig && !phoenixConfig && !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig && !aliyunConfig && !mlflowConfig && !databricksConfig && !tencentConfig
const switchContent = (
<Switch
@@ -185,6 +189,32 @@ const ConfigPopup: FC<PopupProps> = ({
/>
)
const mlflowPanel = (
<ProviderPanel
type={TracingProvider.mlflow}
readOnly={readOnly}
config={mlflowConfig}
hasConfigured={!!mlflowConfig}
onConfig={handleOnConfig(TracingProvider.mlflow)}
isChosen={chosenProvider === TracingProvider.mlflow}
onChoose={handleOnChoose(TracingProvider.mlflow)}
key="mlflow-provider-panel"
/>
)
const databricksPanel = (
<ProviderPanel
type={TracingProvider.databricks}
readOnly={readOnly}
config={databricksConfig}
hasConfigured={!!databricksConfig}
onConfig={handleOnConfig(TracingProvider.databricks)}
isChosen={chosenProvider === TracingProvider.databricks}
onChoose={handleOnChoose(TracingProvider.databricks)}
key="databricks-provider-panel"
/>
)
const tencentPanel = (
<ProviderPanel
type={TracingProvider.tencent}
@@ -221,6 +251,12 @@ const ConfigPopup: FC<PopupProps> = ({
if (aliyunConfig)
configuredPanels.push(aliyunPanel)
if (mlflowConfig)
configuredPanels.push(mlflowPanel)
if (databricksConfig)
configuredPanels.push(databricksPanel)
if (tencentConfig)
configuredPanels.push(tencentPanel)
@@ -251,6 +287,12 @@ const ConfigPopup: FC<PopupProps> = ({
if (!aliyunConfig)
notConfiguredPanels.push(aliyunPanel)
if (!mlflowConfig)
notConfiguredPanels.push(mlflowPanel)
if (!databricksConfig)
notConfiguredPanels.push(databricksPanel)
if (!tencentConfig)
notConfiguredPanels.push(tencentPanel)
@@ -258,6 +300,10 @@ const ConfigPopup: FC<PopupProps> = ({
}
const configuredProviderConfig = () => {
if (currentProvider === TracingProvider.mlflow)
return mlflowConfig
if (currentProvider === TracingProvider.databricks)
return databricksConfig
if (currentProvider === TracingProvider.arize)
return arizeConfig
if (currentProvider === TracingProvider.phoenix)
@@ -316,6 +362,8 @@ const ConfigPopup: FC<PopupProps> = ({
{langfusePanel}
{langSmithPanel}
{opikPanel}
{mlflowPanel}
{databricksPanel}
{weavePanel}
{arizePanel}
{phoenixPanel}

View File

@@ -8,5 +8,7 @@ export const docURL = {
[TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions',
[TracingProvider.weave]: 'https://weave-docs.wandb.ai/',
[TracingProvider.aliyun]: 'https://help.aliyun.com/zh/arms/tracing-analysis/untitled-document-1750672984680',
[TracingProvider.mlflow]: 'https://mlflow.org/docs/latest/genai/',
[TracingProvider.databricks]: 'https://docs.databricks.com/aws/en/mlflow3/genai/tracing/',
[TracingProvider.tencent]: 'https://cloud.tencent.com/document/product/248/116531',
}

View File

@@ -8,12 +8,12 @@ import {
import { useTranslation } from 'react-i18next'
import { usePathname } from 'next/navigation'
import { useBoolean } from 'ahooks'
import type { AliyunConfig, ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import { TracingProvider } from './type'
import TracingIcon from './tracing-icon'
import ConfigButton from './config-button'
import cn from '@/utils/classnames'
import { AliyunIcon, ArizeIcon, LangfuseIcon, LangsmithIcon, OpikIcon, PhoenixIcon, TencentIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing'
import { AliyunIcon, ArizeIcon, DatabricksIcon, LangfuseIcon, LangsmithIcon, MlflowIcon, OpikIcon, PhoenixIcon, TencentIcon, WeaveIcon } 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'
@@ -71,6 +71,8 @@ const Panel: FC = () => {
[TracingProvider.opik]: OpikIcon,
[TracingProvider.weave]: WeaveIcon,
[TracingProvider.aliyun]: AliyunIcon,
[TracingProvider.mlflow]: MlflowIcon,
[TracingProvider.databricks]: DatabricksIcon,
[TracingProvider.tencent]: TencentIcon,
}
const InUseProviderIcon = inUseTracingProvider ? providerIconMap[inUseTracingProvider] : undefined
@@ -82,8 +84,10 @@ const Panel: FC = () => {
const [opikConfig, setOpikConfig] = useState<OpikConfig | null>(null)
const [weaveConfig, setWeaveConfig] = useState<WeaveConfig | null>(null)
const [aliyunConfig, setAliyunConfig] = useState<AliyunConfig | null>(null)
const [mlflowConfig, setMLflowConfig] = useState<MLflowConfig | null>(null)
const [databricksConfig, setDatabricksConfig] = useState<DatabricksConfig | null>(null)
const [tencentConfig, setTencentConfig] = useState<TencentConfig | null>(null)
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig || tencentConfig)
const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig || mlflowConfig || databricksConfig || tencentConfig)
const fetchTracingConfig = async () => {
const getArizeConfig = async () => {
@@ -121,6 +125,16 @@ const Panel: FC = () => {
if (!aliyunHasNotConfig)
setAliyunConfig(aliyunConfig as AliyunConfig)
}
const getMLflowConfig = async () => {
const { tracing_config: mlflowConfig, has_not_configured: mlflowHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.mlflow })
if (!mlflowHasNotConfig)
setMLflowConfig(mlflowConfig as MLflowConfig)
}
const getDatabricksConfig = async () => {
const { tracing_config: databricksConfig, has_not_configured: databricksHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.databricks })
if (!databricksHasNotConfig)
setDatabricksConfig(databricksConfig as DatabricksConfig)
}
const getTencentConfig = async () => {
const { tracing_config: tencentConfig, has_not_configured: tencentHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.tencent })
if (!tencentHasNotConfig)
@@ -134,6 +148,8 @@ const Panel: FC = () => {
getOpikConfig(),
getWeaveConfig(),
getAliyunConfig(),
getMLflowConfig(),
getDatabricksConfig(),
getTencentConfig(),
])
}
@@ -174,6 +190,10 @@ const Panel: FC = () => {
setWeaveConfig(null)
else if (provider === TracingProvider.aliyun)
setAliyunConfig(null)
else if (provider === TracingProvider.mlflow)
setMLflowConfig(null)
else if (provider === TracingProvider.databricks)
setDatabricksConfig(null)
else if (provider === TracingProvider.tencent)
setTencentConfig(null)
if (provider === inUseTracingProvider) {
@@ -221,6 +241,8 @@ const Panel: FC = () => {
opikConfig={opikConfig}
weaveConfig={weaveConfig}
aliyunConfig={aliyunConfig}
mlflowConfig={mlflowConfig}
databricksConfig={databricksConfig}
tencentConfig={tencentConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}
@@ -258,6 +280,8 @@ const Panel: FC = () => {
opikConfig={opikConfig}
weaveConfig={weaveConfig}
aliyunConfig={aliyunConfig}
mlflowConfig={mlflowConfig}
databricksConfig={databricksConfig}
tencentConfig={tencentConfig}
onConfigUpdated={handleTracingConfigUpdated}
onConfigRemoved={handleTracingConfigRemoved}

View File

@@ -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 { AliyunConfig, ArizeConfig, LangFuseConfig, LangSmithConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type'
import { TracingProvider } from './type'
import { docURL } from './config'
import {
@@ -22,10 +22,10 @@ import Divider from '@/app/components/base/divider'
type Props = {
appId: string
type: TracingProvider
payload?: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig | null
payload?: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | MLflowConfig | DatabricksConfig | TencentConfig | null
onRemoved: () => void
onCancel: () => void
onSaved: (payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig) => void
onSaved: (payload: ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | MLflowConfig | DatabricksConfig | TencentConfig) => void
onChosen: (provider: TracingProvider) => void
}
@@ -77,6 +77,21 @@ const aliyunConfigTemplate = {
endpoint: '',
}
const mlflowConfigTemplate = {
tracking_uri: '',
experiment_id: '',
username: '',
password: '',
}
const databricksConfigTemplate = {
experiment_id: '',
host: '',
client_id: '',
client_secret: '',
personal_access_token: '',
}
const tencentConfigTemplate = {
token: '',
endpoint: '',
@@ -96,7 +111,7 @@ const ProviderConfigModal: FC<Props> = ({
const isEdit = !!payload
const isAdd = !isEdit
const [isSaving, setIsSaving] = useState(false)
const [config, setConfig] = useState<ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | TencentConfig>((() => {
const [config, setConfig] = useState<ArizeConfig | PhoenixConfig | LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | MLflowConfig | DatabricksConfig | TencentConfig>((() => {
if (isEdit)
return payload
@@ -118,6 +133,12 @@ const ProviderConfigModal: FC<Props> = ({
else if (type === TracingProvider.aliyun)
return aliyunConfigTemplate
else if (type === TracingProvider.mlflow)
return mlflowConfigTemplate
else if (type === TracingProvider.databricks)
return databricksConfigTemplate
else if (type === TracingProvider.tencent)
return tencentConfigTemplate
@@ -211,6 +232,20 @@ const ProviderConfigModal: FC<Props> = ({
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Endpoint' })
}
if (type === TracingProvider.mlflow) {
const postData = config as MLflowConfig
if (!errorMessage && !postData.tracking_uri)
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Tracking URI' })
}
if (type === TracingProvider.databricks) {
const postData = config as DatabricksConfig
if (!errorMessage && !postData.experiment_id)
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Experiment ID' })
if (!errorMessage && !postData.host)
errorMessage = t('common.errorMsg.fieldRequired', { field: 'Host' })
}
if (type === TracingProvider.tencent) {
const postData = config as TencentConfig
if (!errorMessage && !postData.token)
@@ -513,6 +548,81 @@ const ProviderConfigModal: FC<Props> = ({
/>
</>
)}
{type === TracingProvider.mlflow && (
<>
<Field
label={t(`${I18N_PREFIX}.trackingUri`)!}
labelClassName='!text-sm'
value={(config as MLflowConfig).tracking_uri}
isRequired
onChange={handleConfigChange('tracking_uri')}
placeholder={'http://localhost:5000'}
/>
<Field
label={t(`${I18N_PREFIX}.experimentId`)!}
labelClassName='!text-sm'
isRequired
value={(config as MLflowConfig).experiment_id}
onChange={handleConfigChange('experiment_id')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.experimentId`) })!}
/>
<Field
label={t(`${I18N_PREFIX}.username`)!}
labelClassName='!text-sm'
value={(config as MLflowConfig).username}
onChange={handleConfigChange('username')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.username`) })!}
/>
<Field
label={t(`${I18N_PREFIX}.password`)!}
labelClassName='!text-sm'
value={(config as MLflowConfig).password}
onChange={handleConfigChange('password')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.password`) })!}
/>
</>
)}
{type === TracingProvider.databricks && (
<>
<Field
label={t(`${I18N_PREFIX}.experimentId`)!}
labelClassName='!text-sm'
value={(config as DatabricksConfig).experiment_id}
onChange={handleConfigChange('experiment_id')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.experimentId`) })!}
isRequired
/>
<Field
label={t(`${I18N_PREFIX}.databricksHost`)!}
labelClassName='!text-sm'
value={(config as DatabricksConfig).host}
onChange={handleConfigChange('host')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.databricksHost`) })!}
isRequired
/>
<Field
label={t(`${I18N_PREFIX}.clientId`)!}
labelClassName='!text-sm'
value={(config as DatabricksConfig).client_id}
onChange={handleConfigChange('client_id')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.clientId`) })!}
/>
<Field
label={t(`${I18N_PREFIX}.clientSecret`)!}
labelClassName='!text-sm'
value={(config as DatabricksConfig).client_secret}
onChange={handleConfigChange('client_secret')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.clientSecret`) })!}
/>
<Field
label={t(`${I18N_PREFIX}.personalAccessToken`)!}
labelClassName='!text-sm'
value={(config as DatabricksConfig).personal_access_token}
onChange={handleConfigChange('personal_access_token')}
placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.personalAccessToken`) })!}
/>
</>
)}
</div>
<div className='my-8 flex h-8 items-center justify-between'>
<a

View File

@@ -7,7 +7,7 @@ import {
import { useTranslation } from 'react-i18next'
import { TracingProvider } from './type'
import cn from '@/utils/classnames'
import { AliyunIconBig, ArizeIconBig, LangfuseIconBig, LangsmithIconBig, OpikIconBig, PhoenixIconBig, TencentIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing'
import { AliyunIconBig, ArizeIconBig, DatabricksIconBig, LangfuseIconBig, LangsmithIconBig, MlflowIconBig, OpikIconBig, PhoenixIconBig, TencentIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing'
import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general'
const I18N_PREFIX = 'app.tracing'
@@ -31,6 +31,8 @@ const getIcon = (type: TracingProvider) => {
[TracingProvider.opik]: OpikIconBig,
[TracingProvider.weave]: WeaveIconBig,
[TracingProvider.aliyun]: AliyunIconBig,
[TracingProvider.mlflow]: MlflowIconBig,
[TracingProvider.databricks]: DatabricksIconBig,
[TracingProvider.tencent]: TencentIconBig,
})[type]
}

View File

@@ -6,6 +6,8 @@ export enum TracingProvider {
opik = 'opik',
weave = 'weave',
aliyun = 'aliyun',
mlflow = 'mlflow',
databricks = 'databricks',
tencent = 'tencent',
}
@@ -55,6 +57,21 @@ export type AliyunConfig = {
endpoint: string
}
export type MLflowConfig = {
tracking_uri: string
experiment_id: string
username: string
password: string
}
export type DatabricksConfig = {
experiment_id: string
host: string
client_id: string
client_secret: string
personal_access_token: string
}
export type TencentConfig = {
token: string
endpoint: string