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های انتشار",