From 2806edb70b2117fc67bf2aa1852b7cc63ac264cb Mon Sep 17 00:00:00 2001 From: hieheihei <270985384@qq.com> Date: Fri, 16 May 2025 17:17:11 +0800 Subject: [PATCH] update aliyun trace --- api/app.py | 29 ++- api/core/ops/aliyun_trace/__init__.py | 0 api/core/ops/aliyun_trace/aliyun_trace.py | 192 ++++++++++++++++++ .../aliyun_trace/data_exporter/__init__.py | 0 .../aliyun_trace/data_exporter/traceclient.py | 80 ++++++++ .../ops/aliyun_trace/entities/__init__.py | 0 .../entities/aliyun_trace_entity.py | 21 ++ api/core/ops/entities/config_entity.py | 10 + api/core/ops/ops_trace_manager.py | 11 + .../[appId]/overview/tracing/config-popup.tsx | 34 +++- .../[appId]/overview/tracing/config.ts | 1 + .../[appId]/overview/tracing/panel.tsx | 28 ++- .../tracing/provider-config-modal.tsx | 47 ++++- .../overview/tracing/provider-panel.tsx | 3 +- .../[appId]/overview/tracing/type.ts | 7 + .../assets/public/tracing/aliyun-icon-big.svg | 7 + .../assets/public/tracing/aliyun-icon.svg | 7 + .../icons/src/public/tracing/AliyunIcon.json | 18 ++ .../icons/src/public/tracing/AliyunIcon.tsx | 16 ++ .../src/public/tracing/AliyunIconBig.json | 18 ++ .../src/public/tracing/AliyunIconBig.tsx | 16 ++ .../base/icons/src/public/tracing/index.ts | 2 + web/i18n/en-US/app.ts | 4 + web/i18n/zh-Hans/app.ts | 4 + web/models/app.ts | 4 +- 25 files changed, 526 insertions(+), 33 deletions(-) create mode 100644 api/core/ops/aliyun_trace/__init__.py create mode 100644 api/core/ops/aliyun_trace/aliyun_trace.py create mode 100644 api/core/ops/aliyun_trace/data_exporter/__init__.py create mode 100644 api/core/ops/aliyun_trace/data_exporter/traceclient.py create mode 100644 api/core/ops/aliyun_trace/entities/__init__.py create mode 100644 api/core/ops/aliyun_trace/entities/aliyun_trace_entity.py create mode 100644 web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg create mode 100644 web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg create mode 100644 web/app/components/base/icons/src/public/tracing/AliyunIcon.json create mode 100644 web/app/components/base/icons/src/public/tracing/AliyunIcon.tsx create mode 100644 web/app/components/base/icons/src/public/tracing/AliyunIconBig.json create mode 100644 web/app/components/base/icons/src/public/tracing/AliyunIconBig.tsx diff --git a/api/app.py b/api/app.py index 4f393f6c20..499a06772f 100644 --- a/api/app.py +++ b/api/app.py @@ -1,4 +1,3 @@ -import os import sys @@ -17,20 +16,20 @@ else: # It seems that JetBrains Python debugger does not work well with gevent, # so we need to disable gevent in debug mode. # If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent. - if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}: - from gevent import monkey - - # gevent - monkey.patch_all() - - from grpc.experimental import gevent as grpc_gevent # type: ignore - - # grpc gevent - grpc_gevent.init_gevent() - - import psycogreen.gevent # type: ignore - - psycogreen.gevent.patch_psycopg() + # if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}: + # from gevent import monkey + # + # # gevent + # monkey.patch_all() + # + # from grpc.experimental import gevent as grpc_gevent # type: ignore + # + # # grpc gevent + # grpc_gevent.init_gevent() + # + # import psycogreen.gevent # type: ignore + # + # psycogreen.gevent.patch_psycopg() from app_factory import create_app diff --git a/api/core/ops/aliyun_trace/__init__.py b/api/core/ops/aliyun_trace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/aliyun_trace/aliyun_trace.py b/api/core/ops/aliyun_trace/aliyun_trace.py new file mode 100644 index 0000000000..be6a0db3ac --- /dev/null +++ b/api/core/ops/aliyun_trace/aliyun_trace.py @@ -0,0 +1,192 @@ +import datetime +import json +import uuid +from typing import Optional + +from core.ops.aliyun_trace.data_exporter.traceclient import TraceClient +from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData +from core.ops.base_trace_instance import BaseTraceInstance +from core.ops.entities.config_entity import AliyunConfig +from core.ops.entities.trace_entity import ( + BaseTraceInfo, + DatasetRetrievalTraceInfo, + GenerateNameTraceInfo, + MessageTraceInfo, + ModerationTraceInfo, + SuggestedQuestionTraceInfo, + ToolTraceInfo, + WorkflowTraceInfo, +) +from models import EndUser, db + + +def convert_to_trace_id(uuid_v4:str) -> int: + try: + uuid_obj = uuid.UUID(uuid_v4) + return uuid_obj.int + except Exception as e: + raise ValueError(f"Invalid UUID input: {e}") + +def convert_to_span_id(uuid_v4:str, span_type:str) -> int: + try: + uuid_obj = uuid.UUID(uuid_v4) + except Exception as e: + raise ValueError(f"Invalid UUID input: {e}") + + type_hash = hash(span_type) & 0xFFFFFFFFFFFFFFFF + span_id = (uuid_obj.int & 0xFFFFFFFFFFFFFFFF) ^ type_hash + + return span_id + +def convert_datetime_to_nanoseconds(start_time_a: Optional[datetime]) -> Optional[int]: + if start_time_a is None: + return None + timestamp_in_seconds = start_time_a.timestamp() + timestamp_in_nanoseconds = int(timestamp_in_seconds * 1e9) + return timestamp_in_nanoseconds + + +class AliyunDataTrace(BaseTraceInstance): + + def __init__( + self, + aliyun_config: AliyunConfig, + ): + super().__init__(aliyun_config) + endpoint = aliyun_config.endpoint+aliyun_config.license_key+'/api/otlp/traces' + self.trace_client = TraceClient(service_name=aliyun_config.app_name,endpoint=endpoint) + + def trace(self, trace_info: BaseTraceInfo): + if isinstance(trace_info, WorkflowTraceInfo): + self.workflow_trace(trace_info) + if isinstance(trace_info, MessageTraceInfo): + self.message_trace(trace_info) + if isinstance(trace_info, ModerationTraceInfo): + pass + if isinstance(trace_info, SuggestedQuestionTraceInfo): + pass + if isinstance(trace_info, DatasetRetrievalTraceInfo): + self.dataset_retrieval_trace(trace_info) + if isinstance(trace_info, ToolTraceInfo): + self.tool_trace(trace_info) + if isinstance(trace_info, GenerateNameTraceInfo): + pass + + def api_check(self): + # todo + return True + + def workflow_trace(self, trace_info: WorkflowTraceInfo): + pass + + def message_trace(self, trace_info: MessageTraceInfo): + # get message file data + file_list = trace_info.file_list + metadata = trace_info.metadata + message_data = trace_info.message_data + if message_data is None: + return + message_id = trace_info.message_id + + user_id = message_data.from_account_id + if message_data.from_end_user_id: + end_user_data: Optional[EndUser] = ( + db.session.query(EndUser).filter(EndUser.id == message_data.from_end_user_id).first() + ) + if end_user_data is not None: + user_id = end_user_data.session_id + + message_span = SpanData( + trace_id=convert_to_trace_id(message_id), + parent_span_id=None, + span_id=convert_to_span_id(message_id,'message'), + name='message', + start_time=convert_datetime_to_nanoseconds(trace_info.start_time), + end_time=convert_datetime_to_nanoseconds(trace_info.end_time), + attributes={ + 'gen_ai.session.id': trace_info.metadata.get('conversation_id',''), + 'gen_ai.user.id':str(user_id), + 'gen_ai.span.kind':'CHAIN', + 'gen_ai.framework': 'dify', + + 'input.value':json.dumps(trace_info.inputs), + 'output.value': str(trace_info.outputs), + } + ) + self.trace_client.add_span(message_span) + + llm_span = SpanData( + trace_id=convert_to_trace_id(message_id), + parent_span_id=convert_to_span_id(message_id,'message'), + span_id=convert_to_span_id(message_id,'llm'), + name='llm', + start_time=convert_datetime_to_nanoseconds(trace_info.start_time), + end_time=convert_datetime_to_nanoseconds(trace_info.end_time), + attributes={ + 'gen_ai.session.id': trace_info.metadata.get('conversation_id',''), + 'gen_ai.user.id': str(user_id), + 'gen_ai.span.kind':'LLM', + 'gen_ai.framework': 'dify', + + 'gen_ai.prompt_template.template': 'todo', + 'gen_ai.model_name':trace_info.message_data.model_id, + + 'input.value':json.dumps(trace_info.inputs), + 'output.value': str(trace_info.outputs), + } + ) + + self.trace_client.add_span(llm_span) + + + def dataset_retrieval_trace(self, trace_info: DatasetRetrievalTraceInfo): + if trace_info.message_data is None: + return + message_id = trace_info.message_id + + span_data = SpanData( + trace_id=convert_to_trace_id(message_id), + parent_span_id=convert_to_span_id(message_id,'message'), + span_id=convert_to_span_id(message_id,'dataset_retrieval'), + name='dataset_retrieval', + start_time=convert_datetime_to_nanoseconds(trace_info.start_time), + end_time=convert_datetime_to_nanoseconds(trace_info.end_time), + attributes={ + 'gen_ai.session.id': 'todo', + 'gen_ai.user.id': 'todo', + 'gen_ai.span.kind': 'RETRIEVER', + 'gen_ai.framework': 'dify', + + 'gen_ai.operation.name': 'TASK', + 'retrieval.query':str(trace_info.inputs), + 'retrieval.document ':str(trace_info.documents) + } + ) + self.trace_client.add_span(span_data) + + def tool_trace(self, trace_info: ToolTraceInfo): + if trace_info.message_data is None: + return + message_id = trace_info.message_id + + span_data = SpanData( + trace_id=convert_to_trace_id(message_id), + parent_span_id=convert_to_span_id(message_id,'message'), + span_id=convert_to_span_id(message_id,'tool'), + name='tool', + start_time=convert_datetime_to_nanoseconds(trace_info.start_time), + end_time=convert_datetime_to_nanoseconds(trace_info.end_time), + attributes={ + 'gen_ai.session.id': 'todo', + 'gen_ai.user.id': 'todo', + 'gen_ai.span.kind': 'Tool', + 'gen_ai.framework': 'dify', + + 'tool.name': trace_info.tool_name, + 'tool.description': trace_info.tool_name, + 'tool.parameters': json.dumps(trace_info.tool_inputs), + 'input.value': json.dumps(trace_info.inputs), + 'output.value': str(trace_info.tool_outputs), + } + ) + self.trace_client.add_span(span_data) diff --git a/api/core/ops/aliyun_trace/data_exporter/__init__.py b/api/core/ops/aliyun_trace/data_exporter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/aliyun_trace/data_exporter/traceclient.py b/api/core/ops/aliyun_trace/data_exporter/traceclient.py new file mode 100644 index 0000000000..b69295ee66 --- /dev/null +++ b/api/core/ops/aliyun_trace/data_exporter/traceclient.py @@ -0,0 +1,80 @@ +import socket +from collections.abc import Sequence + +from opentelemetry import trace as trace_api +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.util.instrumentation import InstrumentationScope +from opentelemetry.semconv.resource import ResourceAttributes + +from configs import dify_config +from core.ops.aliyun_trace.entities.aliyun_trace_entity import SpanData + + +class TraceClient: + def __init__(self,service_name,endpoint): + self.endpoint = endpoint + self.resource = Resource( + attributes={ + ResourceAttributes.SERVICE_NAME: service_name, + ResourceAttributes.SERVICE_VERSION: f"dify-{dify_config.CURRENT_VERSION}-{dify_config.COMMIT_SHA}", + ResourceAttributes.DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}", + ResourceAttributes.HOST_NAME: socket.gethostname(), + } + ) + self.span_builder = SpanBuilder(self.resource) + self.exporter = OTLPSpanExporter(endpoint=endpoint) + + def add_span(self,span_data:SpanData): + span:ReadableSpan = self.span_builder.build_span(span_data) + self.export([span]) + + def export(self,spans:Sequence[ReadableSpan]): + self.exporter.export(spans) + +class SpanBuilder: + def __init__(self, resource): + self.resource = resource + self.instrumentation_scope = InstrumentationScope( + __name__, + "", + None, + None, + ) + + def build_span(self, span_data: SpanData) -> ReadableSpan: + span_context = trace_api.SpanContext( + trace_id=span_data.trace_id, + span_id=span_data.span_id, + is_remote=False, + trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), + trace_state=None, + ) + + parent_span_context = None + if span_data.parent_span_id is not None: + parent_span_context = trace_api.SpanContext( + trace_id=span_data.trace_id, + span_id=span_data.parent_span_id, + is_remote=False, + trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), + trace_state=None, + ) + + span = ReadableSpan( + name=span_data.name, + context=span_context, + parent=parent_span_context, + resource=self.resource, + attributes=span_data.attributes, + events=span_data.events, + links=span_data.links, + kind=trace_api.SpanKind.INTERNAL, + status=span_data.status, + start_time=span_data.start_time, + end_time=span_data.end_time, + instrumentation_scope=self.instrumentation_scope, + ) + return span + diff --git a/api/core/ops/aliyun_trace/entities/__init__.py b/api/core/ops/aliyun_trace/entities/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/aliyun_trace/entities/aliyun_trace_entity.py b/api/core/ops/aliyun_trace/entities/aliyun_trace_entity.py new file mode 100644 index 0000000000..ac9300db50 --- /dev/null +++ b/api/core/ops/aliyun_trace/entities/aliyun_trace_entity.py @@ -0,0 +1,21 @@ +from collections.abc import Sequence +from typing import Optional + +from opentelemetry import trace as trace_api +from opentelemetry.sdk.trace import Event, Status, StatusCode +from pydantic import BaseModel, Field + + +class SpanData(BaseModel): + model_config = {"arbitrary_types_allowed": True} + + trace_id: int = Field(..., description="The unique identifier for the trace.") + parent_span_id: Optional[int] = Field(None, description="The ID of the parent span, if any.") + span_id: int = Field(..., description="The unique identifier for this span.") + name: str = Field(..., description="The name of the span.") + attributes: dict[str, str] = Field(default_factory=dict, description="Attributes associated with the span.") + events: Sequence[Event] = Field(default_factory=list, description="Events recorded in the span.") + links: Sequence[trace_api.Link] = Field(default_factory=list, description="Links to other spans.") + status: Status = Field(default=Status(StatusCode.UNSET), description="The status of the span.") + start_time: int = Field(..., description="The start time of the span in nanoseconds.") + end_time: int = Field(..., description="The end time of the span in nanoseconds.") diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index f2d1bd305a..399fe1d337 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -8,6 +8,7 @@ class TracingProviderEnum(StrEnum): LANGSMITH = "langsmith" OPIK = "opik" WEAVE = "weave" + ALIYUN = "aliyun" class BaseTracingConfig(BaseModel): @@ -109,6 +110,15 @@ class WeaveConfig(BaseTracingConfig): return v +class AliyunConfig(BaseTracingConfig): + """ + Model class for Aliyun tracing config. + """ + + app_name: str = "dify_app" + license_key: str + endpoint: str + OPS_FILE_PATH = "ops_trace/" OPS_TRACE_FAILED_KEY = "FAILED_OPS_TRACE" diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index dc4cfc48db..61ef5c315c 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -85,6 +85,17 @@ class OpsTraceProviderConfigMap(dict[str, dict[str, Any]]): "trace_instance": WeaveDataTrace, } + case TracingProviderEnum.ALIYUN: + from core.ops.aliyun_trace.aliyun_trace import AliyunDataTrace + from core.ops.entities.config_entity import AliyunConfig + + return { + "config_class": AliyunConfig, + "secret_keys": ["license_key"], + "other_keys": ["endpoint","app_name"], + "trace_instance": AliyunDataTrace, + } + case _: raise KeyError(f"Unsupported tracing provider: {provider}") diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 0efc5082a4..927e63dccd 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -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, OpikConfig, WeaveConfig } from './type' +import type { AliyunConfig, LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import ProviderConfigModal from './provider-config-modal' import Indicator from '@/app/components/header/indicator' @@ -27,7 +27,8 @@ export type PopupProps = { langFuseConfig: LangFuseConfig | null opikConfig: OpikConfig | null weaveConfig: WeaveConfig | null - onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void + aliyunConfig: AliyunConfig | null + onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig) => void onConfigRemoved: (provider: TracingProvider) => void } @@ -42,6 +43,7 @@ const ConfigPopup: FC = ({ langFuseConfig, opikConfig, weaveConfig, + aliyunConfig, onConfigUpdated, onConfigRemoved, }) => { @@ -65,7 +67,7 @@ const ConfigPopup: FC = ({ } }, [onChooseProvider]) - const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => { + const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig) => { onConfigUpdated(currentProvider!, payload) hideConfigModal() }, [currentProvider, hideConfigModal, onConfigUpdated]) @@ -75,8 +77,8 @@ const ConfigPopup: FC = ({ hideConfigModal() }, [currentProvider, hideConfigModal, onConfigRemoved]) - const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig - const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig + const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig && aliyunConfig + const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig && !aliyunConfig const switchContent = ( = ({ key="weave-provider-panel" /> ) + + const aliyunPanel = ( + + ) const configuredProviderPanel = () => { const configuredPanels: JSX.Element[] = [] @@ -152,6 +167,9 @@ const ConfigPopup: FC = ({ if (weaveConfig) configuredPanels.push(weavePanel) + if (aliyunConfig) + configuredPanels.push(aliyunPanel) + return configuredPanels } @@ -170,6 +188,9 @@ const ConfigPopup: FC = ({ if (!weaveConfig) notConfiguredPanels.push(weavePanel) + if (!aliyunConfig) + notConfiguredPanels.push(aliyunPanel) + return notConfiguredPanels } @@ -180,6 +201,8 @@ const ConfigPopup: FC = ({ return langFuseConfig if (currentProvider === TracingProvider.opik) return opikConfig + if (currentProvider === TracingProvider.aliyun) + return aliyunConfig return weaveConfig } @@ -225,6 +248,7 @@ const ConfigPopup: FC = ({ {langSmithPanel} {opikPanel} {weavePanel} + {aliyunPanel} ) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts index 5d3c4076bd..e1dd184472 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts @@ -5,4 +5,5 @@ export const docURL = { [TracingProvider.langfuse]: 'https://docs.langfuse.com', [TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions', [TracingProvider.weave]: 'https://weave-docs.wandb.ai/', + [TracingProvider.aliyun]: 'https://arms.console.aliyun.com/', } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 8575117c41..a32a4fcaba 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -1,18 +1,22 @@ 'use client' import type { FC } from 'react' import React, { useCallback, useEffect, useState } from 'react' -import { - RiArrowDownDoubleLine, -} from '@remixicon/react' +import { RiArrowDownDoubleLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { usePathname } from 'next/navigation' import { useBoolean } from 'ahooks' -import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' +import type { AliyunConfig, LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import TracingIcon from './tracing-icon' import ConfigButton from './config-button' import cn from '@/utils/classnames' -import { LangfuseIcon, LangsmithIcon, OpikIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' +import { + AliyunIcon, + LangfuseIcon, + LangsmithIcon, + OpikIcon, + 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' @@ -84,13 +88,16 @@ const Panel: FC = () => { ? OpikIcon : inUseTracingProvider === TracingProvider.weave ? WeaveIcon + : inUseTracingProvider === TracingProvider.aliyun + ? AliyunIcon : LangsmithIcon const [langSmithConfig, setLangSmithConfig] = useState(null) const [langFuseConfig, setLangFuseConfig] = useState(null) const [opikConfig, setOpikConfig] = useState(null) const [weaveConfig, setWeaveConfig] = useState(null) - const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig) + const [aliyunConfig, setAliyunConfig] = useState(null) + const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || aliyunConfig) const fetchTracingConfig = async () => { const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) @@ -105,6 +112,9 @@ const Panel: FC = () => { const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave }) if (!weaveHasNotConfig) setWeaveConfig(weaveConfig as WeaveConfig) + const { tracing_config: aliyunConfig, has_not_configured: aliyunHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.aliyun }) + if (!aliyunHasNotConfig) + setAliyunConfig(aliyunConfig as AliyunConfig) } const handleTracingConfigUpdated = async (provider: TracingProvider) => { @@ -118,6 +128,8 @@ const Panel: FC = () => { setOpikConfig(tracing_config as OpikConfig) else if (provider === TracingProvider.weave) setWeaveConfig(tracing_config as WeaveConfig) + else if (provider === TracingProvider.aliyun) + setAliyunConfig(tracing_config as AliyunConfig) } const handleTracingConfigRemoved = (provider: TracingProvider) => { @@ -129,6 +141,8 @@ const Panel: FC = () => { setOpikConfig(null) else if (provider === TracingProvider.weave) setWeaveConfig(null) + else if (provider === TracingProvider.aliyun) + setAliyunConfig(null) if (provider === inUseTracingProvider) { handleTracingStatusChange({ enabled: false, @@ -189,6 +203,7 @@ const Panel: FC = () => { langFuseConfig={langFuseConfig} opikConfig={opikConfig} weaveConfig={weaveConfig} + aliyunConfig={aliyunConfig} onConfigUpdated={handleTracingConfigUpdated} onConfigRemoved={handleTracingConfigRemoved} controlShowPopup={controlShowPopup} @@ -224,6 +239,7 @@ const Panel: FC = () => { langFuseConfig={langFuseConfig} opikConfig={opikConfig} weaveConfig={weaveConfig} + aliyunConfig={aliyunConfig} onConfigUpdated={handleTracingConfigUpdated} onConfigRemoved={handleTracingConfigRemoved} controlShowPopup={controlShowPopup} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index c0b52a9b10..b044388d3d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -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, OpikConfig, WeaveConfig } from './type' +import type { AliyunConfig, LangFuseConfig, LangSmithConfig, OpikConfig, 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?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null + payload?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig | null onRemoved: () => void onCancel: () => void - onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void + onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig) => void onChosen: (provider: TracingProvider) => void } @@ -57,6 +57,12 @@ const weaveConfigTemplate = { endpoint: '', } +const aliyunConfigTemplate = { + app_name: '', + license_key: '', + endpoint: '', +} + const ProviderConfigModal: FC = ({ appId, type, @@ -70,7 +76,7 @@ const ProviderConfigModal: FC = ({ const isEdit = !!payload const isAdd = !isEdit const [isSaving, setIsSaving] = useState(false) - const [config, setConfig] = useState((() => { + const [config, setConfig] = useState((() => { if (isEdit) return payload @@ -83,6 +89,9 @@ const ProviderConfigModal: FC = ({ else if (type === TracingProvider.opik) return opikConfigTemplate + else if (type === TracingProvider.aliyun) + return aliyunConfigTemplate + return weaveConfigTemplate })()) const [isShowRemoveConfirm, { @@ -145,6 +154,11 @@ const ProviderConfigModal: FC = ({ errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) }) } + if (type === TracingProvider.aliyun) { + // todo: check + // const postData = config as AliyunConfig + } + return errorMessage }, [config, t, type]) const handleSave = useCallback(async () => { @@ -194,6 +208,31 @@ const ProviderConfigModal: FC = ({
+ {type === TracingProvider.aliyun && ( + <> + + + + + )} {type === TracingProvider.weave && ( <> { [TracingProvider.langfuse]: LangfuseIconBig, [TracingProvider.opik]: OpikIconBig, [TracingProvider.weave]: WeaveIconBig, + [TracingProvider.aliyun]: AliyunIconBig, })[type] } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts index 386c58974e..ad6b30ee4e 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts @@ -3,6 +3,7 @@ export enum TracingProvider { langfuse = 'langfuse', opik = 'opik', weave = 'weave', + aliyun = 'aliyun', } export type LangSmithConfig = { @@ -30,3 +31,9 @@ export type WeaveConfig = { project: string endpoint: string } + +export type AliyunConfig = { + app_name: string + license_key: string + endpoint: string +} diff --git a/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg b/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg new file mode 100644 index 0000000000..34ad2f5add --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/aliyun-icon-big.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + A + + diff --git a/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg b/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg new file mode 100644 index 0000000000..34ad2f5add --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/aliyun-icon.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + A + + diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIcon.json b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json new file mode 100644 index 0000000000..1dfabda4d8 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json @@ -0,0 +1,18 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "width": "120px", + "height": "16px", + "viewBox": "0 0 120 16", + "version": "1.1" + }, + "children": [ + ] + }, + "name": "WeaveIcon" +} diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIcon.tsx b/web/app/components/base/icons/src/public/tracing/AliyunIcon.tsx new file mode 100644 index 0000000000..5b062b8a86 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/AliyunIcon.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './AliyunIcon.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'AliyunIcon' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json new file mode 100644 index 0000000000..4398b7cd1a --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json @@ -0,0 +1,18 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "width": "124px", + "height": "16px", + "viewBox": "0 0 120 16", + "version": "1.1" + }, + "children": [ + ] + }, + "name": "WeaveIconBig" +} diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.tsx b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.tsx new file mode 100644 index 0000000000..0924f70fbd --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './AliyunIconBig.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'AliyunIconBig' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/index.ts b/web/app/components/base/icons/src/public/tracing/index.ts index 36b59e479b..0d2704158a 100644 --- a/web/app/components/base/icons/src/public/tracing/index.ts +++ b/web/app/components/base/icons/src/public/tracing/index.ts @@ -7,3 +7,5 @@ export { default as OpikIcon } from './OpikIcon' export { default as TracingIcon } from './TracingIcon' export { default as WeaveIconBig } from './WeaveIconBig' export { default as WeaveIcon } from './WeaveIcon' +export { default as AliyunIconBig } from './AliyunIconBig' +export { default as AliyunIcon } from './AliyunIcon' diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 20a80ba4cd..e4982d7a64 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -165,6 +165,10 @@ const translation = { title: 'Weave', description: 'Weave is an open-source platform for evaluating, testing, and monitoring LLM applications.', }, + cms: { + title: 'Aliyun', + description: 'Aliyun Demo', + }, inUse: 'In use', configProvider: { title: 'Config ', diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index a634394cfb..7bd3ee0700 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -176,6 +176,10 @@ const translation = { title: '编织', description: 'Weave是一个开源平台,用于评估、测试和监控大型语言模型应用程序。', }, + aliyun: { + title: '阿里云可观测', + description: '阿里云可观测 Demo', + }, }, appSelector: { label: '应用', diff --git a/web/models/app.ts b/web/models/app.ts index b10ced703a..f7b399d15b 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -1,4 +1,4 @@ -import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' +import type { AliyunConfig, LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' import type { App, AppTemplate, SiteConfig } from '@/types/app' import type { Dependency } from '@/app/components/plugins/types' @@ -166,5 +166,5 @@ export type TracingStatus = { export type TracingConfig = { tracing_provider: TracingProvider - tracing_config: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig + tracing_config: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | AliyunConfig }