diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 3af3e328df..eeb9e1443e 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -644,12 +644,19 @@ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS] ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true" ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true" ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true" + OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get( "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317" ) +OTEL_METRICS_EXPORTER_OTLP_ENDPOINT = os.environ.get( + "OTEL_METRICS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT +) OTEL_EXPORTER_OTLP_INSECURE = ( os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true" ) +OTEL_METRICS_EXPORTER_OTLP_INSECURE = ( + os.environ.get("OTEL_METRICS_EXPORTER_OTLP_INSECURE", "False").lower() == "true" +) OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui") OTEL_RESOURCE_ATTRIBUTES = os.environ.get( "OTEL_RESOURCE_ATTRIBUTES", "" @@ -660,11 +667,21 @@ OTEL_TRACES_SAMPLER = os.environ.get( OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "") OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "") +OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get( + "OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME +) +OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get( + "OTEL_METRICS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD +) OTEL_OTLP_SPAN_EXPORTER = os.environ.get( "OTEL_OTLP_SPAN_EXPORTER", "grpc" ).lower() # grpc or http +OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get( + "OTEL_METRICS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER +).lower() # grpc or http + #################################### # TOOLS/FUNCTIONS PIP OPTIONS diff --git a/backend/open_webui/utils/telemetry/metrics.py b/backend/open_webui/utils/telemetry/metrics.py index f3e82c7dab..75c13ccc0a 100644 --- a/backend/open_webui/utils/telemetry/metrics.py +++ b/backend/open_webui/utils/telemetry/metrics.py @@ -19,37 +19,69 @@ from __future__ import annotations import time from typing import Dict, List, Sequence, Any +from base64 import b64encode from fastapi import FastAPI, Request from opentelemetry import metrics from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) + +from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + OTLPMetricExporter as OTLPHttpMetricExporter, +) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.view import View from opentelemetry.sdk.metrics.export import ( PeriodicExportingMetricReader, ) -from opentelemetry.sdk.resources import SERVICE_NAME, Resource - -from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT +from opentelemetry.sdk.resources import Resource +from open_webui.env import ( + OTEL_SERVICE_NAME, + OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, + OTEL_METRICS_BASIC_AUTH_USERNAME, + OTEL_METRICS_BASIC_AUTH_PASSWORD, + OTEL_METRICS_OTLP_SPAN_EXPORTER, + OTEL_METRICS_EXPORTER_OTLP_INSECURE, +) from open_webui.socket.main import get_active_user_ids from open_webui.models.users import Users _EXPORT_INTERVAL_MILLIS = 10_000 # 10 seconds -def _build_meter_provider() -> MeterProvider: +def _build_meter_provider(resource: Resource) -> MeterProvider: """Return a configured MeterProvider.""" + headers = [] + if OTEL_METRICS_BASIC_AUTH_USERNAME and OTEL_METRICS_BASIC_AUTH_PASSWORD: + auth_string = ( + f"{OTEL_METRICS_BASIC_AUTH_USERNAME}:{OTEL_METRICS_BASIC_AUTH_PASSWORD}" + ) + auth_header = b64encode(auth_string.encode()).decode() + headers = [("authorization", f"Basic {auth_header}")] # Periodic reader pushes metrics over OTLP/gRPC to collector - readers: List[PeriodicExportingMetricReader] = [ - PeriodicExportingMetricReader( - OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT), - export_interval_millis=_EXPORT_INTERVAL_MILLIS, - ) - ] + if OTEL_METRICS_OTLP_SPAN_EXPORTER == "http": + readers: List[PeriodicExportingMetricReader] = [ + PeriodicExportingMetricReader( + OTLPHttpMetricExporter( + endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, headers=headers + ), + export_interval_millis=_EXPORT_INTERVAL_MILLIS, + ) + ] + else: + readers: List[PeriodicExportingMetricReader] = [ + PeriodicExportingMetricReader( + OTLPMetricExporter( + endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, + insecure=OTEL_METRICS_EXPORTER_OTLP_INSECURE, + headers=headers, + ), + export_interval_millis=_EXPORT_INTERVAL_MILLIS, + ) + ] # Optional view to limit cardinality: drop user-agent etc. views: List[View] = [ @@ -70,17 +102,17 @@ def _build_meter_provider() -> MeterProvider: ] provider = MeterProvider( - resource=Resource.create({SERVICE_NAME: OTEL_SERVICE_NAME}), + resource=resource, metric_readers=list(readers), views=views, ) return provider -def setup_metrics(app: FastAPI) -> None: +def setup_metrics(app: FastAPI, resource: Resource) -> None: """Attach OTel metrics middleware to *app* and initialise provider.""" - metrics.set_meter_provider(_build_meter_provider()) + metrics.set_meter_provider(_build_meter_provider(resource)) meter = metrics.get_meter(__name__) # Instruments diff --git a/backend/open_webui/utils/telemetry/setup.py b/backend/open_webui/utils/telemetry/setup.py index 80beec0bbb..cd1f45ea6a 100644 --- a/backend/open_webui/utils/telemetry/setup.py +++ b/backend/open_webui/utils/telemetry/setup.py @@ -26,11 +26,8 @@ from open_webui.env import ( def setup(app: FastAPI, db_engine: Engine): # set up trace - trace.set_tracer_provider( - TracerProvider( - resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) - ) - ) + resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) + trace.set_tracer_provider(TracerProvider(resource=resource)) # Add basic auth header only if both username and password are not empty headers = [] @@ -56,4 +53,4 @@ def setup(app: FastAPI, db_engine: Engine): # set up metrics only if enabled if ENABLE_OTEL_METRICS: - setup_metrics(app) + setup_metrics(app, resource) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index abbac4c544..bd5be1f8e3 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -520,6 +520,8 @@ async def get_tool_servers_data( openapi_data = response.get("openapi", {}) if info and isinstance(openapi_data, dict): + openapi_data["info"] = openapi_data.get("info", {}) + if "name" in info: openapi_data["info"]["title"] = info.get("name", "Tool Server") diff --git a/backend/requirements.txt b/backend/requirements.txt index d48ed8d9c8..4c9bd85ead 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -9,7 +9,7 @@ passlib[bcrypt]==1.7.4 cryptography requests==2.32.4 -aiohttp==3.11.11 +aiohttp==3.12.15 async-timeout aiocache aiofiles @@ -42,14 +42,14 @@ asgiref==3.8.1 # AI libraries openai anthropic -google-genai==1.15.0 +google-genai==1.28.0 google-generativeai==0.8.5 tiktoken langchain==0.3.26 langchain-community==0.3.26 -fake-useragent==2.1.0 +fake-useragent==2.2.0 chromadb==0.6.3 posthog==5.4.0 pymilvus==2.5.0 @@ -75,7 +75,7 @@ docx2txt==0.8 python-pptx==1.0.2 unstructured==0.16.17 nltk==3.9.1 -Markdown==3.7 +Markdown==3.8.2 pypandoc==1.15 pandas==2.2.3 openpyxl==3.1.5 @@ -97,7 +97,7 @@ onnxruntime==1.20.1 faster-whisper==1.1.1 PyJWT[crypto]==2.10.1 -authlib==1.4.1 +authlib==1.6.1 black==25.1.0 langfuse==2.44.0 diff --git a/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte b/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte index d246bb36d9..13bee6e111 100644 --- a/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte +++ b/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte @@ -122,6 +122,11 @@ {:else if token.text.includes(` {:else} - {token.text} + {@const br = token.text.match(//)} + {#if br} +
+ {:else} + {token.text} + {/if} {/if} {/if} diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 77c912d7b2..9fde6f6735 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -82,7 +82,7 @@ const initPinnedModelsSortable = () => { const pinnedModelsList = document.getElementById('pinned-models-list'); - if (pinnedModelsList) { + if (pinnedModelsList && !$mobile) { new Sortable(pinnedModelsList, { animation: 150, onUpdate: async (event) => { diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index fd19b846a0..a4b0ef3a0d 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -1072,7 +1072,7 @@ "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "به خود به عنوان \"کاربر\" اشاره کنید (مثلاً، \"کاربر در حال یادگیری اسپانیایی است\")", "References from": "مراجع از", "Refused when it shouldn't have": "رد شده زمانی که باید نباشد", - "Regenerate": "ری\u200cسازی", + "Regenerate": "تولید مجدد", "Reindex": "فهرست\u200cبندی مجدد", "Reindex Knowledge Base Vectors": "فهرست\u200cبندی مجدد بردارهای پایگاه دانش", "Release Notes": "یادداشت\u200cهای انتشار",